Skip to content

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.

Channel
What they tell you
Run oneMode try
You, to your agent

Hand our latest objection whose condition came true 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(%22Deal.lost%22%2C%20%22Subscription.unsubscribed%22%2C%20%22Subscription.switched%22%2C%20%22Subscription.lapsed%22%2C%20%22Conversation.labeled%22%2C%20%22Consent.granted%22%2C%20%22Issue.resolved%22%2C%20%22Version.deployed%22%2C%20%22Offer.published%22%2C%20%22Organization.repriced%22%2C%20%22Organization.deprecated%22%2C%20%22Company.funded%22%2C%20%22Opening.published%22%2C%20%22Organization.appointed%22%2C%20%22Organization.announced%22%2C%20%22Objection.dated%22%2C%20%22Conversation.answered%22%2C%20%22Content.published%22%2C%20%22Change.reverted%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_1290"}'

# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/decisions?where=on%20%3D%20%22customer_38%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_38%22" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
import { $ } from 'startup-builder'

const { events: [last] } = await $.Event.list({ where: 'name in ("Deal.lost", "Subscription.unsubscribed", "Subscription.switched", "Subscription.lapsed", "Conversation.labeled", "Consent.granted", "Issue.resolved", "Version.deployed", "Offer.published", "Organization.repriced", "Organization.deprecated", "Company.funded", "Opening.published", "Organization.appointed", "Organization.announced", "Objection.dated", "Conversation.answered", "Content.published", "Change.reverted")', 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 (\"Deal.lost\", \"Subscription.unsubscribed\", \"Subscription.switched\", \"Subscription.lapsed\", \"Conversation.labeled\", \"Consent.granted\", \"Issue.resolved\", \"Version.deployed\", \"Offer.published\", \"Organization.repriced\", \"Organization.deprecated\", \"Company.funded\", \"Opening.published\", \"Organization.appointed\", \"Organization.announced\", \"Objection.dated\", \"Conversation.answered\", \"Content.published\", \"Change.reverted\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_1290"}' --try   # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"customer_38\""}'   # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"customer_38\""}'   # Not live yet (gaps #7)
{
  "name": "do",
  "arguments": {
    "code": "const { events: [last] } = await $.Event.list({ where: 'name in (\"Deal.lost\", \"Subscription.unsubscribed\", \"Subscription.switched\", \"Subscription.lapsed\", \"Conversation.labeled\", \"Consent.granted\", \"Issue.resolved\", \"Version.deployed\", \"Offer.published\", \"Organization.repriced\", \"Organization.deprecated\", \"Company.funded\", \"Opening.published\", \"Organization.appointed\", \"Organization.announced\", \"Objection.dated\", \"Conversation.answered\", \"Content.published\", \"Change.reverted\")', 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_502",
    "$type": "Decision",
    "choice": "send-the-one",
    "reason": "Oakridge Flats cancelled Jun 3 saying “owner statements only come out as CSV”; issue_77 shipped statements in the owner portal on Oct 16 and has held all traffic for 3 days, so the proof rule passes",
    "stakes": "low",
    "options": [
      "send-the-one",
      "you-write-it",
      "name-what-it-answers",
      "send-the-correction",
      "keep-holding"
    ],
  …
sample · 4 Not live yetPreview

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

action_3 is the one Message to contact_14, in the thread of Oakridge’s cancel, held 30 minutes so you can stop it. The proof is read again just before it sends.

$694 objection dollars closed against $0 before · before-after, not proof · n 3 · path → arr

Try another: the holderChoiceReason
Oakridge Flats: cancelled Jun 3, “owner statements only come out as CSV”send-the-oneissue_77 shipped portal statements and held all traffic 3 days
Birchway Properties: lost Deal, “we need it to work for 600 units”, a top-tier holderyou-write-ittop tier: the one Message is yours to write
Kestrel Rentals: downgraded, “too slow on mobile, and no bulk edits”name-what-it-answersthe mobile fix shipped; bulk edits have not
Alder & Co: lost Deal, “no e-signing”; the e-sign Change was reverted Oct 11send-the-correctiona fired set’s Change came out: one correction within 24 h

Where it sits: Stranger → Lead → Trialing → Activated → At risk → Churned → Won back · Objection dollars closed → Won back ↑ → ARR added each week → ARR

Worth’s formula: about +9 returns per 100 former-Customer sets fired (Other Startups’ results; no lift of your own yet) × 3 sets a month × $347 Pro ARR ≈ +$94; the Prospects’ sets read beside it as Deals opened. Under First signal: At 3 former-Customer sets a month, 90 days can tell only +40 per 100 or more, so your file reads before-after; a set matures 90 days after it fires.

sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on Issue.resolved · first fire 38m after the proof passed · 9 keys Not live yet

Start it this week

Terminal
npx startup-builder add play objection-watch   # 1 · writes plays/objection-watch.mdx (Not live yet, gaps #5)

# 2 · dry run; below, its 422 on 2026-06-01
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":"objection-watch"}'

# 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":"objection-watch"}'

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

# off: no fire sends; every hold stays in force
curl -X PATCH https://api.sb/leaseline.app/campaigns/objection-watch \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endsAt":"now"}'
422Refusedobjection-watch can’t start yet: 1 check fails and 2 warn.
CheckFixThen
use-when pass · Not live yet, gaps #8Nothing to do: every no with a reason counts.POST /{namespace}/metrics/query
billing-connected fail · Not live yet, gaps #8Connect Stripe so every cancel and downgrade arrives with its reason code.POST /{namespace}/integrations/connect
baseline warn · Not live yet, gaps #8n 9: it reads before-after, not proof, until volume allows a held-back half.POST /{namespace}/metrics/query
owned-by-a-person pass · Not live yet, gaps #8Nothing to do: their fires draft into your queue instead.POST /{namespace}/tasks/{task}/assign
keep-first pass · Not live yet, gaps #8Nothing to do: a Customer at risk or past due hears from their CSM first.POST /{namespace}/tasks/next
customer-ask-cap pass · Not live yet, gaps #3Nothing to do: one open ask per Customer, across every Play.GET /{namespace}/decisions
role-unstaffed warn · Not live yet, gaps #8Staff the CSM and AE, or leave them: replies and big accounts come to you.POST /{namespace}/roles/{role}/staff
JSON
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "objection-watch can’t start yet: 1 check fails and 2 warn.",
  "checks": [
    {
      "check": "use-when",
      "status": "pass",
      "observed": "26 nos a month: lost Deals, cancels, downgrades and stalled trials",
      "doc": "/plays/objection-watch#check-use-when",
      "fix": "Nothing to do: every no with a reason counts.",
      "then": "Metric.query"
    },
    {
      "check": "billing-connected",
      "status": "fail",
      "doc": "/plays/objection-watch#check-billing-connected",
      "fix": "Connect Stripe so every cancel and downgrade arrives with its reason code.",
      "then": "Integration.connect"
    },
    {
      "check": "baseline",
      "status": "warn",
      "observed": "0 of 9 former Customers back in 90 days, $0",
      "doc": "/plays/objection-watch#check-baseline",
      "fix": "n 9: it reads before-after, not proof, until volume allows a held-back half.",
      "then": "Metric.query"
    },
    {
      "check": "owned-by-a-person",
      "status": "pass",
      "observed": "3 holders have a person owner, you",
      "doc": "/plays/objection-watch#check-owned-by-a-person",
      "fix": "Nothing to do: their fires draft into your queue instead.",
      "then": "Task.assign"
    },
    {
      "check": "keep-first",
      "status": "pass",
      "observed": "0 held",
      "doc": "/plays/objection-watch#check-keep-first",
      "fix": "Nothing to do: a Customer at risk or past due hears from their CSM first.",
      "then": "Task.next"
    },
    {
      "check": "customer-ask-cap",
      "status": "pass",
      "observed": "0 open asks",
      "doc": "/plays/objection-watch#check-customer-ask-cap",
      "fix": "Nothing to do: one open ask per Customer, across every Play.",
      "then": "Decision.list"
    },
    {
      "check": "role-unstaffed",
      "status": "warn",
      "roles": [
        "success-manager",
        "sales-lead"
      ],
      "owned": 3,
      "doc": "/plays/objection-watch#check-role-unstaffed",
      "fix": "Staff the CSM and AE, or leave them: replies and big accounts come to you.",
      "then": "Role.staff"
    }
  ]
}

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

CheckFix (one call)Your timeWait
use-when · pass: 26 nos a month (gaps #8)POST /{namespace}/metrics/query on objection-dollars-closed1 minnone
billing-connected · failPOST /{namespace}/integrations/connect with system recurring-billing (Stripe)5 minthe first sync, about 10 min
baseline · warn: 0 of 9 in 90 daysPOST /{namespace}/metrics/query; it fills Before in Your number0 minnone
owned-by-a-person · keep-first · customer-ask-cap · pass: 3 owned by you; 0 held; 0 open asks (gaps #3, #8)nothing; each runs again on every fire0 minnone
role-unstaffed · warn: 3 holders have a person owner, youPOST /{namespace}/roles/{role}/staff, or leave it: replies and top-tier sets come to you0 minnone

Your number · Before $0 · Now $694 objection dollars closed · Target $1,500 by Dec 31 · before-after, not proof · n 3 matured · Other Startups +9 per 100, ■■■□ Likely

Capturing and coding a no costs about $0.01, and each fire about $0.04 from your Domain (Worker cost). The condition checks read your own Issues and public pages at no cost. That comes to about $1 a month at 25 nos and 5 fires.

When it starts

WhenTrigger idStarts onWindowHow it gets in
A Customer cancels, with a reasonsubscription-endingevent on Subscription.unsubscribed where reason != nullcaptured within 1 hPOST /{namespace}/integrations/connect (Stripe); your cancel form posts the reason
A Deal is lost, with the code and their own wordsdeal-lostevent on Deal.lost where lossReason != nullcaptured within 1 hPOST /{namespace}/deals/{deal}/lose with reason
An Issue linked to open objections ships and holds all trafficissue-resolvedevent on Issue.resolved where linkedObjections > 0 and heldAllTraffic = true30dPOST /{namespace}/issues/{issue}/resolve
+ 10 more: downgrades, stalled trials and not-now replies that open a set; and rival pages, new buyers, dated conditions and trust items that can close one (?lanes=all)
One signed delivery · StartupBuilder-Signature
{
  "$id": "https://api.sb/leaseline.app/events/event_1290",
  "$type": "Event",
  "name": "Issue.resolved",
  "at": "2026-10-19T06:00:11Z",
  "seq": 1290,
  "data": {
    "issue": {
      "$id": "https://api.sb/leaseline.app/issues/issue_77",
      "title": "Owner statements in the owner portal",
      "resolvedAt": "2026-10-16",
      "heldAllTraffic": true
    },
    "linkedObjections": [
      "objection_12",
      "objection_19"
    ],
    "openDollars": 694
  }
}
Reproduce it in test
npx startup-builder trigger Issue.resolved --like event_1290   # replays the delivery in test
npx startup-builder clock advance --by 30m   # sends the one Message (action_3) after its hold
npx startup-builder listen Subscription.created --forward-to http://localhost:3000/hook

Who runs it

CSM · your agent (role:success-manager, worker_5) holds the Key Result. A reply that wants to come back goes to AE · Priya Shah (person) (worker_3, staffed on day 9); top-tier sets and every set whose proof cites a named rival come to you (Dana Okafor, person, worker_1), because the CSM Role has no person. Your number links to Did it work.

OptionRung todayEvidence (passed of n)Moves up atDrops back whenYou can
Send the one MessageIt does it, you can stop it11 of 11 approved uneditedmet on day 71 (the first 10 fires, none not-answered)a not-answered reply, or the double-fire Stop → You approve each onePOST /{namespace}/actions/{action}/cancel inside the 30-minute hold
Name what it answers · Keep holdingIt does it, you can stop it3 of 3 · 41 of 41the samethe samethe same
You write it · Send the correctionYou approve each one4 of 4 · 1 of 1never: top-tier sets and corrections stay yours—POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse
Change a proof ruleYou approve each one1 merged of 1never—POST /{namespace}/changes/{change}/merge · POST /{namespace}/changes/{change}/revert
Never: a second Message on one set, a discount, a shipped date for something unshippedRefused, every time0 sentnever—none: the Stop holds it

What it reads · What it may call. It reads the holder’s own words, the Issue and the proof source, and never a Visit, an open or a return to the site. A held holder is held for every Play: other Plays’ pool steps refuse a promotional touch until the set closes. Its tools are POST /{namespace}/messages/{message}/schedule, POST /{namespace}/tasks/{task}/assign, POST /{namespace}/content/draft, and any other call becomes an Approval under the Role’s Mandate.

The brief (folded): compiled Agent-register Markdown · play:objection-watch@1 · each line names its record key
You hold the CSM Key Result: $1,500 objection dollars closed by 2026-12-31.      ← worker, metric
On each condition that comes true, choose one option and say why.                ← triggers, chooses
Quote their words with their date; never make them stronger.                     ← copyRules 1
Cite the proof with its date, or do not send.                                    ← copyRules 3
Name only what the proof answers; no date for anything unshipped.                ← copyRules 6–7
Say this is the only Message about it, with one ask.                             ← copyRules 10–11

The slow loop, as a Change. decision_288 (2026-08-17, weekly) read 4 mobile-speed sets whose proof was the release note alone, with 2 not-answered replies. change_19 made the proof rule read the measured page time as well (your file, line 7), and Dana merged it with POST /{namespace}/changes/{change}/merge. Replayed on past fires, POST /{namespace}/changes/{change}/review held 2 of the 4 back; read back at +30d, 0 not-answered of 3 fires, n 3, ■□□□ Too early to tell (Example, day 108).

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 each one when it does not; POST /{namespace}/roles/{role}/staff seats it.

Terminal
npx startup-builder eval --scenario objection-watch-118 --agent "claude -p" --seat success-manager   # seated: 21 of 21 sets falsified correctly, 0 double fires, $0.04 a fire · yours: 19 of 21 → You approve each one

What it can do next

OptionPicked whenDecided byRunsHands toIf it’s just you
Send the one Message send-the-onethe proof rule passes and answers the whole objectionYour rule (line 5)objection-fire-one in the thread of their no → POST /{namespace}/messages/{message}/schedule, then a 30-day reply windowa reply that wants to come back: POST /{namespace}/tasks/{task}/assign → AE · Priya Shah, due 1 business day · Take this oneyou
You write it you-write-ita top-tier holder, or the proof cites a named rivalYour rule (line 6)objection-fire-founder → POST /{namespace}/tasks/{task}/assign to you with the set and the proofyou write and send it · Take this oneyou
Name what it answers name-what-it-answersthe proof answers part of the objectionAgent’s call, with its reasonobjection-fire-one, naming only what shipped → POST /{namespace}/content/draftthe same as Send the one Messageyou
Send the correction send-the-correctiona Change behind a fired set was revertedYour rule (the Revert stop)objection-correction in the same thread, within 24 hyou approve it · Take this oneyou
Keep holding keep-holdingthe proof fails or is stale, the set is held back, or keep-first appliesAgent’s call, with its reasonnothing; the hold stays, and names what it waits onthe hold, until the condition is true or it expiresthe same
When they write backFixture replyWhat happensWho answersBy whenCall
Interested“Good timing. Can we restart on 160 units?”over 150 units: a Quote; under it, the Pro restart linkAE · Priya Shah (person)1 business dayPOST /{namespace}/quotes/{quote}/send
Send me pricing“What would it cost us now?”the Pricing Page’s list Price, no private PriceCSM · your agent5 minPOST /{namespace}/conversations/{conversation}/answer
Not now“Maybe after our January budget.”the set closes; January is recorded as a new condition datenobodyat onceGET /{namespace}/decisions
Wrong person“Our new ops lead, Sam, owns tools now.”asks them to bring Sam in; it never writes to an address from their textCSM · your agent1 business dayPOST /{namespace}/conversations/{conversation}/answer
Out of officean auto-reply until Oct 28the 30-day reply window runs on; nothing else sendsnobodythe return datePOST /{namespace}/messages/{message}/schedule
Question it can’t answer“Do statements include the owner’s tax forms?”To a person, the question in the BriefAE · Priya Shah, or you1 business dayPOST /{namespace}/conversations/{conversation}/escalate
Stop“Please don’t contact us again.”nothing else sends from any Play, and the set closesnobodyat oncePOST /{namespace}/contacts/{contact}/suppress

When it hands to you

Replies that want to come back go to AE · Priya Shah (person); top-tier sets and sets citing a rival come to you, and before day 9 every reply did. 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_391",
    "$type": "Task",
    "assignee": "worker_3",
    "due": "2026-09-23T15:40:00Z",
    "on": "customer_29",
    "brief": {
      "who": "Sable Street Homes, cancelled May 19",
      "said": "Good timing. Can we restart on 160 units?",
      "objection": "no bulk lease edits",
      "proof": "issue_64, shipped Sep 15",
      "chose": "send-the-one",
      "why": "the proof passed; the whole objection is answered",
      "never": [
        "a discount",
        "a second Message on this set"
      ]
    }
  },
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_342",
    "$type": "Task",
    "assignee": "worker_1",
    "due": "2026-09-04T17:00:00Z",
    "on": "lead_47",
    "brief": {
      "who": "Birchway Properties, lost Deal Jul 2",
      "said": "We need it to work for 600 units.",
      "chose": "you-write-it",
      "why": "a top-tier holder",
      "proof": "the 600-unit load test, published Sep 1"
    }
  }
]

The receiver answers with POST /{namespace}/tasks/{task}/claim (Take this one), POST /{namespace}/tasks/{task}/return with a reason, or POST /{namespace}/actions/{action}/redirect, each a one-call window. Once a person claims, the agent sends nothing more on that set, and every reply goes to the claimer (claimedByPerson, Not live yet, gaps #7). 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, 3 of 3 claimed within 1 business day, median 2 h 05 min, n 3; you, 4 of 4 sent within 3 days, n 4, ■□□□ Too early to tell beside Other Startups’ 91 % on time. Your queue: counted by kind: in week 1, 18 labels to confirm and 0 fires; after week 3, about 3 labels and 1 fire a week, plus the Monday Issue ranking.

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 fire to a holder 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)a fire to a Customer who is at risk, past due or has 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 Playnext-outcome holds them until Nov 18.GET /{namespace}/decisions
proof-drift (the record’s Proof drift stop)a fire whose proof source changed or vanished since it was capturedRe-read the proof; the fire sends only if it passes again.GET /{namespace}/issues/{id}
label-backlog (the record’s Label backlog hold)a fire on a set whose label nobody confirmedConfirm the label on the lost Deal, then the set can fire.PATCH /{namespace}/deals/{id}
market-hold (the record’s Market hold)a fire to a Market that is unresolved or failed its row checkResolve the holder’s Market; the fire never retries another way.PATCH /{namespace}/organizations/{id}
fact-check (Not live yet)a line the proof, the claims source or the holder’s own record does not backCut the claim, or name the record that backs it.POST /{namespace}/content/draft
Stop (pauses the Play)Pauses whenThen
hold-breachany Play sends a promotional touch to a held holder; 2 in 30 days pause every Play’s pool stepthat Play’s sends pause; an audit opens
double-firea second fire on one setevery fire halts until you review the fire ledger
not-answerednot-answered replies over 5 % of a kind’s last 40 fires, or 2 in 7 daysfires of that kind halt
reverta Change behind fired sets is revertedfires on its Issue halt; the correction sends within 24 h
freshnessthe Monday canary failscondition checks pause until it passes
kill-switchyou press itevery fire stops; every hold stays in force
complaintstop and hostile replies over 0.3 % of a stream’s last 1,000, or 3 in 7 daysfires pause on that stream
bouncehard bounces over 2 % of a day’s fires, at 20 or morethe stream pauses; its queue is re-verified

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

422RefusedThe fire to Kestrel Rentals did not send: it says both of their reasons are fixed, and the proof shows one.
CheckFixThen
fact-check fail · Not live yet, gaps #1Cut the claim, or name the record that backs it.POST /{namespace}/content/draft
proof-drift pass—GET /{namespace}/issues/{id}
JSON
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The fire to Kestrel Rentals did not send: it says both of their reasons are fixed, and the proof shows one.",
  "checks": [
    {
      "check": "fact-check",
      "status": "fail",
      "doc": "/plays/objection-watch#check-fact-check",
      "fix": "Cut the claim, or name the record that backs it.",
      "then": "Content.draft"
    },
    {
      "check": "proof-drift",
      "status": "pass"
    }
  ]
}

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

The Stops that fired, as Campaign.paused reasons · sample
[
  {
    "reason": {
      "limit": "hold-breach",
      "observed": "1 promotional touch from lead-nurture to a held holder",
      "bound": "0",
      "window": "30d"
    },
    "doc": "/plays/objection-watch#stop-hold-breach",
    "task": "task_377"
  }
]

Did it work

At 3 former-Customer sets 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. Only sets 90 days past their fire count (rule n). Each figure’s call is POST /{namespace}/metrics/query by node, and every node above the leaf is Not live yet (gaps #4).

$694 objection dollars closed against $0 before · before-after, not proof · n 3 · path → arr

Chose
send-the-one
Why
Oakridge Flats cancelled Jun 3 saying “owner statements only come out as CSV”; issue_77 shipped statements in the owner portal on Oct 16 and has held all traffic for 3 days, so the proof rule passes
From
send-the-one · you-write-it · name-what-it-answers · send-the-correction · keep-holding
Expects
Subscription.created by customer_38 within 90d of the fire
Reopens when
no inbound Message.created from contact_14 within 30d: the set closes and nothing else sends
By
worker_5
JSON
{
  "$id": "decisions/decision_502",
  "$type": "Decision",
  "choice": "send-the-one",
  "reason": "Oakridge Flats cancelled Jun 3 saying “owner statements only come out as CSV”; issue_77 shipped statements in the owner portal on Oct 16 and has held all traffic for 3 days, so the proof rule passes",
  "stakes": "low",
  "options": [
    "send-the-one",
    "you-write-it",
    "name-what-it-answers",
    "send-the-correction",
    "keep-holding"
  ],
  "by": "worker_5",
  "on": "customer_38",
  "expected": "Subscription.created by customer_38 within 90d of the fire",
  "reopensWhen": "no inbound Message.created from contact_14 within 30d: the set closes and nothing else sends"
}
NodeNow (140 days)Before (the 90 days, per 140)AddedConfidenceBasis
objection-dollars-closed$694: 2 of 3 matured former-Customer sets back on Pro · 2 still maturing · Booked $694: credited, not all caused by this Play$0: 0 of 9 cancels back— · Worth about +$100 a month—before-after, not proof · Other Startups’ results +9 returns per 100, ■■■□ Likely, n 1,460
reactivated-arr ↑$1,041$347——before-after; includes 1 return this Play never fired on
net-new-arr · arrcarried from reactivated-arrcarried——carried

Before against after reads former Customers who came back in this window against the 90 days before it, scaled to 140 days; it is not proof, and Other Startups’ results print beside it. Beside the path, the Prospects’ 14 fired sets opened 4 Deals and won 1 on the $2,400 plan (+$2,393 ARR), read as opened-deals-and-trials.

How it moves it: each holder heard once, a median 38 minutes after the proof passed, in the thread of their own no, where before nobody wrote back at all (objection-dollars-closed by fire latency).

Guardrails: before against after: contraction-arr, $0 from returners who switched down, against $0, and hold breaches, 1 in 140 days, against a Stop at 2 in 30.

The Campaign card · sample
{
  "$id": "https://api.sb/leaseline.app/campaigns/objection-watch",
  "$type": "Campaign",
  "play": "play:objection-watch@1",
  "path": [
    "objection-dollars-closed",
    "reactivated-arr",
    "net-new-arr",
    "arr"
  ],
  "experiment": {
    "metric": "objection-dollars-closed",
    "unit": "usd",
    "heldBack": 2,
    "decision": null
  },
  "readout": {
    "value": null,
    "n": 3,
    "maturing": 2,
    "interval": null,
    "readability": "inadmissible",
    "basis": "before-after",
    "pooled": true
  },
  "ledger": {
    "spend": 2,
    "workerCost": 1,
    "hours": 4.6,
    "pipelineArr": 4786,
    "wonArr": 3087,
    "projectedArr": 94,
    "paybackDays": 4
  }
}

Outside benchmark: 5 % to 10 % of churned subscribers come back on their own (a subscription-analytics vendor’s billing data, 2026-09-30; a base rate, not a lift).

Before it · After it. Before it: every lost Deal from instant-response and cold-email, every cancel from usage-upsell’s Customers and every stalled trial opens a set here. After it: a reply that wants to come back reaches the AE, Not now feeds lead-nurture with a dated condition, and new condition dates feed moment-outreach. While a set stands, every other Play’s pool step holds the holder.

Example

Leaseline ran it from 2026-06-01 to 2026-10-19 (140 days): 118 nos captured, 15 held by former Customers and 103 by Prospects. 21 sets came true, 2 were held back at random, and 19 fired: 5 former Customers’ and 14 Prospects’. Read every captured thread at ?threads=all; three follow the tables.

What happened, by optionSetsRepliesCame back or opened a DealWon (credited, not all caused by this Play)
Send the one Message1152 former Customers back, 3 Deals2 on Pro (+$694 ARR)
You write it431 Deal1 on the $2,400 plan (+$2,393 ARR)
Name what it answers · Send the correction · Keep holding3 · 1 · 411 · 0 · 000
All fires · held back19 · 29 · —2 back, 4 Deals · 0$3,087 · $0
At 20 nos a month (the Use it when volume)about 13 fires in 140 daysbefore-after, not proof—Other Startups’ results: +9 returns per 100, ■■■□ Likely, n 1,460
DayEventWhat happenedYour time
0event_101 Campaign.createdthe three steps; the dry run fails billing-connected20 min
0–21event_133 Objection.captured, first of 118you confirmed 31 labels in the first 3 weeks; every set held its holder1 h 50 min
9event_190 Role.staffedAE · Priya Shah (person) takes role:sales-lead—
52event_611 Subscription.createdthe first former Customer back: Sable Street Homes, on Pro—
71event_788 Role.updatedpromoted: fires send after a 30-minute hold; top-tier sets stay yours—
78 · 108event_802 Change.merged · event_1004 Readout.createdthe mobile proof rule reads page time: read back 0 not-answered of 35 min
96event_915 Campaign.pausedhold-breach: lead-nurture touched a held holder; its pool step was fixed the same day15 min
1–140event_140 Approval.created, first of 19you approved fires and wrote 4; Monday Issue rankings, from the POST /{namespace}/tasks/next log2 h 30 min · total 4.6 h measured, 4 h asked

Leaseline → Sable Street Homes, Sep 22, 09:12, in the thread of their cancel · On May 19 you wrote: “We cancelled because there are no bulk lease edits.”¹ Bulk edits shipped on Sep 15 and have run on every account since (release note, Sep 15).² This is the only note about it, and nothing else follows unless you reply.³ Want the restart link? *Signed with the Role’s disclosure line and the stop line.*⁴

Sable Street, Sep 22, 14:40 · “Good timing. Can we restart on 160 units?” The Brief reached Priya at 14:41; she claimed it at 15:02 and sent the Quote the next morning (When it hands to you).

Kestrel Rentals, 2026-09-08 · the fire fact-check refused, then the rewrite that sent
- Good news: mobile is fast now and bulk edits are live, so everything you asked for is fixed.
+ On Jul 2 you named mobile speed and bulk edits. Mobile pages now load in 1.2 s (measured Sep 7); bulk edits have not shipped.

Birchway Properties, 2026-09-03 · set held since a lost Deal on Jul 2: “We need it to work for 600 units.” · the 600-unit load test published Sep 1 passed the proof rule, and the set went to Dana as a top-tier holder (task_342). Dana wrote and sent the one Message on Sep 4 at 11:20, inside the promised time; Birchway booked a call that afternoon.

¹ Their own words, verbatim, with their date. ² The proof, with its date. ³ The only Message about this objection. ⁴ The fixed signature and stop lines, compiled from the Role’s Settings.

Your settings

plays/objection-watch.mdx
---
holdout: 0.1      # 1 set in 10 that comes true gets no Message for 90 days
---
Record every no in their own words, and hold them from every other Play.
One Message, with proof, when the reason stops being true.
Accounts over 300 units are mine to write.
A speed fix counts only once the measured page time shows it.
Text
$ npx startup-builder show plays/objection-watch
holdout 0.1 · top tier over 300 units · proof reads page time     your file  plays/objection-watch.mdx:2,4,5,6,7 (Ask 2026-06-01; line 7 by change_19, 2026-08-17)
family keep · worker role:success-manager, owns never · chooses 5 (3 rule, 2 read) · sequences objection-fire-one, objection-fire-founder, objection-correction · approval delay-window · checks owned-by-a-person, keep-first, customer-ask-cap, proof-drift, label-backlog, market-hold, fact-check · stops 8 · placements gmail, outlook + the founder’s LinkedIn tray    catalog  play:objection-watch@1 (Not live yet, gaps #5)

Every key from the catalog

Key Id What it is
Tactic objection-capture Objection capture: Turns every lost Deal, cancel, downgrade, stalled trial and not-now reply into a coded, quoted, priced objection within 1 hour of the no. Starts at Sends nothing.
Tactic condition-offer Condition offer: Lets the holder choose, once and after the no completes, the one condition under which the Startup writes again, recorded with Consent. Starts at You approve the first wave.
Tactic condition-compile Condition compile: Turns an objection into a checkable condition with a proof rule and an expiry, and writes the hold every other Play reads. Starts at Sends nothing.
Tactic objection-ledger Objection ledger: Ranks the Issue queue by the open objection dollars each Issue would falsify, and shows pricing and the comparison Pages the dollars behind their nos. Starts at Sends nothing.
Tactic condition-check Condition check: Tests open objection sets each time a condition Trigger fires, and marks a set falsified only when its proof rule passes. Starts at Sends nothing.
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.
Tactic objection-fire Objection fire: Sends exactly one Message per falsified objection set, in the thread of the original no, quoting the holder’s words with the proof that answers them. Starts at It does it, you can stop it.
Tactic 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.
Tactic sending-stop-loss Sending stop loss: Reads the Play’s own bounces, replies and seed tests on every send, and rests, pauses or retires a Mailbox, Domain, cell or the Play. Starts at Sends nothing.
Tactic objection-readout Objection readout: Reads objection dollars closed against the holdout by condition kind, with coverage, hold breaches, fire latency and each fix’s gain beside it. Starts at Sends nothing.
Trigger deal-lost Deal lost: event on Deal.lost, window 1d.
Trigger subscription-ending Subscription ending: event on Subscription.unsubscribed, window 1d.
Trigger subscription-downgraded Subscription downgraded: event on Subscription.switched, window 1d.
Trigger trial-stalled Trial stalled: state on Subscription.lapsed, window 7d.
Trigger objection-replied Objection replied: event on Conversation.labeled, window 1d.
Trigger condition-set Condition set: event on Consent.granted, window 1d.
Trigger issue-resolved Issue resolved: event on Issue.resolved, window 30d.
Trigger loss-reason-resolved Loss reason resolved: event on Version.deployed, window 30d.
Trigger offer-changed Offer changed: event on Offer.published, window 30d.
Trigger competitor-price Competitor price: event on Organization.repriced, window 30d.
Trigger competitor-changelog Competitor changelog: event on Organization.deprecated, window 30d.
Trigger funding Funding: at on Company.funded, window 45d.
Trigger hiring Opening published: event on Opening.published, window 30d.
Trigger new-executive New executive: event on Organization.appointed, window 45d.
Trigger threshold-announced Threshold announced: threshold on Organization.announced, window 30d.
Trigger condition-date Condition date: at on Objection.dated, window 14d.
Trigger not-now-date Not now date: at on Conversation.answered, window 14d.
Trigger outcome-fact Outcome fact: event on Content.published, window 30d.
Trigger trust-item-published Trust item published: event on Content.published, window 30d.
Trigger change-reverted Change reverted: event on Change.reverted, window 1h.
Sequence objection-fire-one Objection fire one: 2 steps; it ends on Any reply; A return: a restore, a new Deal or a paid Order; A bounce; Suppression at any scope; A never on the condition Page; A revert before send.
Sequence objection-fire-founder Objection fire founder: 6 steps; it ends on Any reply; A return: a restore, a new Deal or a paid Order; A bounce; Suppression at any scope; The Mandate paused or revoked.
Sequence objection-correction Objection correction: 1 steps; it ends on Any reply; Suppression at any scope.
Benchmark objection-churn-return-base Share of churned subscribers who come back within three months: 5–10 % (A subscription-analytics vendor’s billing data, published by its affiliated analyst; a base rate, not a lift, 2026-09-30).

Compiled, not written: edit the Play record objection-watch 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.