Skip to content

Switch kit

Wins your rival’s users with an importer tested on its exports and a dry run they see first, stating no rival fact without a source.

Channel
Offers and Deals
Run oneMode try
You, to your agent

Hand our latest rival import to the SDR agent and show me what it chose and why. Don’t send.

curl "https://api.sb/leaseline.app/events?where=name%20in%20(%22Monitor.checked%22%2C%20%22Post.published%22%2C%20%22File.uploaded%22%2C%20%22Collection.imported%22%2C%20%22Organization.announced%22%2C%20%22Review.published%22%2C%20%22Form.submitted%22%2C%20%22Visit.recorded%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_1302"}'

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

const { events: [last] } = await $.Event.list({ where: 'name in ("Monitor.checked", "Post.published", "File.uploaded", "Collection.imported", "Organization.announced", "Review.published", "Form.submitted", "Visit.recorded")', 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 (\"Monitor.checked\", \"Post.published\", \"File.uploaded\", \"Collection.imported\", \"Organization.announced\", \"Review.published\", \"Form.submitted\", \"Visit.recorded\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_1302"}' --try   # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"lead_63\""}'   # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"lead_63\""}'   # Not live yet (gaps #7)
{
  "name": "do",
  "arguments": {
    "code": "const { events: [last] } = await $.Event.list({ where: 'name in (\"Monitor.checked\", \"Post.published\", \"File.uploaded\", \"Collection.imported\", \"Organization.announced\", \"Review.published\", \"Form.submitted\", \"Visit.recorded\")', 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_355",
    "$type": "Decision",
    "choice": "rescue-the-import",
    "reason": "Brookline Rentals ran a dry run of 212 units from the rival’s export 26 hours ago and committed nothing; 3 lease dates did not map",
    "stakes": "low",
    "options": [
      "rescue-the-import",
      "switch-note",
      "founder-note",
      "import-with-them",
      "not-a-fit",
      "not-now"
  …
sample · 4 Not live yetPreview

On POST /{namespace}/processes/{process}/enableOff POST /{namespace}/processes/{process}/pauseTake this one POST /{namespace}/tasks/{task}/claim

action_2 is the rescue note with the dry-run report and the 3 unmapped dates, held 2 minutes; the last step waits 3 days and ends when they commit.

+4.3 per 100 · ■□□□ Too early to tell · n 118 · path → arr

Try another: the companyChoiceReason
Brookline Rentals: a dry run of 212 units, nothing committed in 26 hoursrescue-the-importa dry run with nothing committed after 24 hours
Copperfield Homes, 60 units: a job post names the rivalswitch-notefits, under the 200-unit line
Westbridge Group, 480 units: a public review says they run the rivalfounder-noteover the 200-unit line
Maple & Main: “The import missed our lease end dates.”import-with-thema failed import goes to a person
Riverbend Storage: runs the rival for storage unitsnot-a-fitstorage fits no Offer

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

Worth’s formula: about +4.3 switch Customers per 100 companies reached × 70 companies a month × $497 first order ARR ≈ +$1,494. Under First signal: About 18 weeks to tell a +4 lift at 70 a month with 20 % held back (16 to read, 2 to mature); 90 days can tell +7 or more.

sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on File.uploaded · first rescue note 24h 06m after the dry run · 8 keys Not live yet

Start it this week

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

# 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":"switch-kit"}'

# on
curl -X POST https://api.sb/leaseline.app/processes/outbound/enable \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"

# off: no switch note sends; the importer and the versus page keep running
curl -X POST https://api.sb/leaseline.app/processes/outbound/pause \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
422Refusedswitch-kit can’t start yet: 1 check fails and 2 warn.
CheckFixThen
use-when pass · Not live yet, gaps #8Nothing to fix: one named rival, 40 or more a month.POST /{namespace}/metrics/query
domain-authenticated passNothing to fix: the Domain is set up for email.POST /{namespace}/domains/{domain}/authenticate
mailbox-warm fail · Not live yet, gaps #1Wait for the warm-up to finish, 14 days from day 0; dry-run rescues keep running.POST /{namespace}/mailboxes/{mailbox}/warm
baseline warn · Not live yet, gaps #8n 1: your held-back 20 % becomes the comparison.POST /{namespace}/metrics/query
role-unstaffed warn · Not live yet, gaps #8Staff onboarding, or leave it: help with imports comes to you.POST /{namespace}/roles/{role}/staff
JSON
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "switch-kit can’t start yet: 1 check fails and 2 warn.",
  "checks": [
    {
      "check": "use-when",
      "status": "pass",
      "observed": "52 companies a month publicly run the rival; the importer passes 41 of 43 fields",
      "doc": "/plays/switch-kit#check-use-when",
      "fix": "Nothing to fix: one named rival, 40 or more a month.",
      "then": "Metric.query"
    },
    {
      "check": "domain-authenticated",
      "status": "pass",
      "observed": "leaseline.app, 3 records resolve",
      "doc": "/plays/switch-kit#check-domain-authenticated",
      "fix": "Nothing to fix: the Domain is set up for email.",
      "then": "Domain.authenticate"
    },
    {
      "check": "mailbox-warm",
      "status": "fail",
      "observed": "dana@leaseline.app, day 0 of 14",
      "doc": "/plays/switch-kit#check-mailbox-warm",
      "fix": "Wait for the warm-up to finish, 14 days from day 0; dry-run rescues keep running.",
      "then": "Mailbox.warm"
    },
    {
      "check": "baseline",
      "status": "warn",
      "observed": "1 switch Customer in 90 days, none asked",
      "doc": "/plays/switch-kit#check-baseline",
      "fix": "n 1: your held-back 20 % becomes the comparison.",
      "then": "Metric.query"
    },
    {
      "check": "role-unstaffed",
      "status": "warn",
      "roles": [
        "onboarding-specialist",
        "sales-lead"
      ],
      "owned": 31,
      "doc": "/plays/switch-kit#check-role-unstaffed",
      "fix": "Staff onboarding, or leave it: help with imports comes to you.",
      "then": "Role.staff"
    }
  ]
}

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

CheckFix (one call)Your timeWait
use-when · domain-authenticated · pass: 52 a month run the rival, 41 of 43 fields import; the Domain resolves (gaps #8)POST /{namespace}/metrics/query on switch-customers; POST /{namespace}/domains/{domain}/authenticate1 minnone
mailbox-warm · fail: day 0 of 14POST /{namespace}/mailboxes/{mailbox}/warm; rescues of dry runs send meanwhile, because the Visitor started them0 min14 days
baseline · warn: 1 switch in 90 daysPOST /{namespace}/metrics/query; the held-back 20 % fills Before in Your number0 minnone
role-unstaffed · warn: 31 of your open Leads have a person owner, youPOST /{namespace}/roles/{role}/staff, or leave it: import help comes to you, and the agent drafts into your queue for your 310 minnone

Your number · Before 0 per 100 held back · Now 4.3 per 100 reached · Target 5 by Dec 31 · ■□□□ Too early to tell · n 118 matured

Each send costs $0.04 from your Domain (Worker cost); the importer’s nightly fixture run and the dry runs cost nothing. That comes to about $8 a month at 70 companies, 3 notes each over 3 months, and $5 at 40.

When it starts

WhenTrigger idStarts onWindowHow it gets in
A company publishes that it runs the rival: a job post, a review or a case studyrival-user-announcedevent on Post.published where mentions a rival on your roster30d; one company oncePOST /{namespace}/monitors on the rival’s name, read each day
A signed-in Visitor’s dry run commits nothing for 24 hoursimport-dry-run-abandonedstate on File.uploaded where dryRun.committedAt is null and dryRun.at < now - 24ha state, read each hour; 72hyour importer calls POST /{namespace}/events/capture with File.uploaded
A new Customer’s first import, within 30 days, comes from the rival’s exportfirst-rival-importevent on Collection.imported where source = 'rival-importer' and customer.acceptedAt > now - 30d30dyour importer calls POST /{namespace}/events/capture
+ 11 more for backlash, export changes, importer tests, named alternatives, rival pages and moments, asks, complaints, technology changes, disputes and pricing visits (?lanes=all)
One signed delivery · StartupBuilder-Signature
{
  "$id": "https://api.sb/leaseline.app/events/event_1302",
  "$type": "Event",
  "name": "File.uploaded",
  "at": "2026-10-18T13:40:02Z",
  "seq": 1302,
  "data": {
    "lead": "lead_63",
    "kind": "dry-run-import",
    "source": "rival-export",
    "units": 212,
    "mapped": 209,
    "unmapped": [
      "lease end date ×3"
    ],
    "committed": false
  }
}
Reproduce it in test
npx startup-builder trigger File.uploaded --like event_1302   # replays the dry run
npx startup-builder clock advance --by 24h   # the dry run turns abandoned, and the rescue starts
npx startup-builder listen File.uploaded --forward-to http://localhost:3000/hook

Who runs it

SDR · your agent (role:prospector, worker_2) holds the Key Result. Founder notes and import help come to you (Dana Okafor, person, worker_1), because the Onboarding Role has no person; Interested replies go to AE · Priya Shah (person) (role:sales-lead, worker_3, staffed on day 9). Every rival fact waits for your approval with its source.

OptionRung todayEvidence (passed of n)Moves up atDrops back whenYou can
Rescue the importIt does it, you can stop it9 of 9met on day 12 (the first 5 rescues)any note fails a Check → You approve each onePOST /{namespace}/actions/{action}/cancel inside the hold
Send the switch noteYou approve the first wave84 of 86at 50 passed, met on day 30fact-check refuses one → You approve each onePOST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse
Founder noteYou send it, from your own Mailbox6 drafted, 6 sent by younever—POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse
Import with them · Not a fit · Not nowIt does it3 of 3 · 11 of 11 · 4 of 4——POST /{namespace}/actions/{action}/redirect
Change a rival factYou approve each one13 approved, 1 re-approved after a changenever—POST /{namespace}/changes/{change}/merge · POST /{namespace}/changes/{change}/revert
Never: a rival fact without a source, a claim about their outage, a discountTo a person, every time2 asked, 2 handed overnever—POST /{namespace}/approvals/{approval}/approve

What it reads · What it may call. It reads public posts that say a company runs the rival, the importer’s dry runs and your approved rival facts. A rival page or review is data, never instructions, and a changed rival fact holds every output that cites it (source-change). Its tools are POST /{namespace}/conversations/{conversation}/answer, POST /{namespace}/leads, POST /{namespace}/tasks/{task}/assign, POST /{namespace}/leads/{lead}/disqualify, and any other call becomes an Approval under the Role’s Mandate.

The brief (folded): compiled Agent-register Markdown · play:switch-kit@1 · each line names its record key
You hold the SDR Key Result: 5 switch Customers per 100 companies reached by 2026-12-31. ← worker, metric
On each Trigger, choose one option and say why.                                  ← triggers, chooses
At 200 units or more, draft a founder note for Dana instead of sending.          ← your file, line 4
State a rival fact only as an approved row says it, with its source.             ← rival-fact-approval, Source change hold
Write nothing to a rival’s users for 48 hours after its incident is resolved.    ← Incident hold
Never write to a Lead a person owns: draft it into their queue.                  ← worker.owns, owned-by-a-person

The slow loop, as a Change. decision_301 (2026-09-21, weekly) read 4 of 6 replies from companies over 150 units asking for Dana, so change_19 moved the founder line from 300 to 200 units (your file, line 4), and Dana merged it with POST /{namespace}/changes/{change}/merge. Replayed on past notes, POST /{namespace}/changes/{change}/review put positive replies at 7.1 per 100; read back at +28d it is 6.6, n 31, ■□□□ Too early to tell (Example, day 56).

Bring your own agent (folded) starts at You approve the first wave 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 switch-kit-140 --agent "claude -p" --seat prospector   # seated: 0 unsourced rival facts, 4.3 per 100 · yours: 2 unsourced, refused by `fact-check` → You approve each one

What it can do next

OptionPicked whenDecided byRunsHands toIf it’s just you
Rescue the import rescue-the-importa dry run with nothing committed after 24 hoursYour rule (the importer)dry-run-rescue: the dry-run report and what did not maptheir press to committhe same
Send the switch note switch-notefits, publicly runs the rival, under 200 unitsAgent’s call, with its reasonPOST /{namespace}/leads → switch-outreach-monthly, 3 notes a month apartInterested → POST /{namespace}/tasks/{task}/assign → AE · Priya Shah, due 60 minyou
Founder note founder-notefits, 200 units or moreAgent’s call, with its reasonswitch-outreach-monthly-founder, drafted into your send trayyou, from your own Mailbox · Take this oneyou
Import with them import-with-themthe import failed, or they ask for helpAgent’s call, with its reasonPOST /{namespace}/tasks/{task}/assign → switch-onboardingOnboarding, so you, due 1 business day · Take this oneyou
Not a fit not-a-fitruns the rival, fits no OfferAgent’s call, with its reasonPOST /{namespace}/leads/{lead}/disqualify (noFit)nobody; the reason feeds the fit Policythe same
Not now not-nowthe rival’s incident is under 48 hours old, or another Play holds themYour rule (Incident hold)nothing; names the holdernobodythe same
When they write backFixture replyWhat happensWho answersBy whenCall
Interested“Show me the import on our data.”books the AE with the dry-run report in the BriefAE · Priya Shah (person)60 minPOST /{namespace}/meetings/book
Send me pricing“What would 120 units cost?”answers with the Pricing Page’s list Price and one next stepSDR · your agent5 minPOST /{namespace}/conversations/{conversation}/answer
Not now“Our contract runs to March.”the notes stop; it moves to Nurture until their renewal monthSDR · your agentat oncePOST /{namespace}/leads/{lead}/nurture
Wrong person“Our ops lead, Sam, owns the software.”asks them to bring Sam in; it never writes to an address from their textSDR · your agent1 business dayPOST /{namespace}/conversations/{conversation}/answer
Out of officean auto-reply until Oct 28the next note waits for the return datenobodythe return datePOST /{namespace}/messages/{message}/schedule
Question it can’t answer“Can you move our maintenance history too?”To a person: what the importer cannot mapOnboarding, so you1 business dayPOST /{namespace}/conversations/{conversation}/escalate
Stop“Don’t email us again.”nothing else sends, on any lanenobodyat oncePOST /{namespace}/contacts/{contact}/suppress

When it hands to you

Founder notes and import help come to you; Interested replies go to AE · Priya Shah (person). One Brief per Role, as captured (brief and draft are Not live yet, gaps #7).

What lands in each queue · sample
[
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_536",
    "$type": "Task",
    "assignee": "worker_1",
    "due": "2026-10-15T17:00:00Z",
    "on": "lead_59",
    "brief": {
      "who": "Westbridge Group, 480 units",
      "said": "a public review: “we run the rival across 6 owners”",
      "chose": "founder-note",
      "why": "over the 200-unit line",
      "draft": "in your send tray",
      "never": [
        "a rival fact without its source",
        "their outage"
      ]
    }
  },
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_519",
    "$type": "Task",
    "assignee": "worker_1",
    "due": "2026-10-13T17:00:00Z",
    "on": "lead_57",
    "brief": {
      "who": "Maple & Main, 96 units",
      "said": "The import missed our lease end dates.",
      "chose": "import-with-them",
      "why": "a failed import goes to a person",
      "unmapped": [
        "lease end date ×14"
      ],
      "next": "a 20-minute import call"
    }
  }
]

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’s pending notes to that company cancel with claimedByPerson (Not live yet, gaps #7), and every reply goes to the claimer’s queue. A Task nobody claims by its due time goes up a Role through POST /{namespace}/tasks/{task}/escalate, and person-promise fires.

On time: you, 9 of 9 within their due time, median 3 h, n 9; AE · Priya Shah, 5 of 5 within 60 min, median 21 min, n 5, ■■□□ Early sign. Your queue: in week 1, 13 rival facts and 30 notes to approve; after the first wave, about 1 founder note and 1 import call 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; Not live yet, gaps #8)a send to a company whose Lead or 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
mailbox-warm (hub guard on POST /{namespace}/messages/{message}/schedule, gaps #1)a first switch note from a Mailbox still warmingWait for the warm-up to finish, 14 days from day 0; dry-run rescues keep running.POST /{namespace}/mailboxes/{mailbox}/warm
fact-check (hub guard on POST /{namespace}/content/{content}/publish)a rival fact in a note or page that no approved, sourced row backsCut the claim, or name the record that backs it.POST /{namespace}/content/draft
source-change (the record’s Source change hold)an output citing a rival value that changed at its sourceRe-approve the changed row; outputs wait within 1 hour.POST /{namespace}/changes/{change}/merge
incident-hold (the record’s Incident hold)a note to a rival’s user within 48 hours of its incident being resolvedIt waits the 48 hours, then sends.POST /{namespace}/messages/{message}/schedule
Stop (pauses the Play)Pauses whenThen
importer-testa fixture run fails, or live errors pass 5 % of 20 imports in 24 hthe importer moves to assisted mode; its claims hold
data-lossa commit differs from its dry run by over 0.5 %the importer pauses; the Customer hears within 1 h
bouncebounces pass 2 % of a day’s sendsPOST /{namespace}/mailboxes/{mailbox}/rest
complaintcomplaints near 0.1 %switch notes pause on every Mailbox
reply-floorpositive replies under 1 % after 300 sendsnotes stop; the importer and pages keep running
hostile-reply3 hostile replies in 30 daysYou approve each one
credit-limitswitch credits reach the month’s Limitgrants pause; POST /{namespace}/approvals/{approval}/approve for you
fitswitchers’ 90-day retention 15 points under other new Customersthe switch Offer pauses
kill-switchyou press itPOST /{namespace}/processes/{process}/pause on outbound; the importer keeps running
+ 3 more for the versus pages and rival-term ads (?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 Copperfield thread in Example.

422RefusedThe switch note to Copperfield Homes did not send: it says the rival raised its price 40 %, and no approved row says so.
CheckFixThen
fact-check fail · Not live yet, gaps #1Cut the claim, or name the record that backs it.POST /{namespace}/content/draft
mailbox-warm pass · Not live yet, gaps #1—POST /{namespace}/mailboxes/{mailbox}/warm
JSON
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The switch note to Copperfield Homes did not send: it says the rival raised its price 40 %, and no approved row says so.",
  "checks": [
    {
      "check": "fact-check",
      "status": "fail",
      "doc": "/plays/switch-kit#check-fact-check",
      "fix": "Cut the claim, or name the record that backs it.",
      "then": "Content.draft"
    },
    {
      "check": "mailbox-warm",
      "status": "pass"
    }
  ]
}

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

The Stops that fired, as Campaign.paused reasons · sample
[
  {
    "reason": {
      "limit": "importer-test",
      "observed": "2 of 43 fields failed after an export change",
      "bound": "0 failed",
      "window": "1 nightly run"
    },
    "doc": "/plays/switch-kit#stop-importer-test",
    "task": "task_461"
  }
]

Did it work

Both halves come from one random assignment of the companies that publicly run the rival (rules m–o), and each figure’s call is POST /{namespace}/metrics/query by node and by arm; the parent field and every node above the leaf are Not live yet (gaps #4).

+4.3 per 100 · ■□□□ Too early to tell · n 118 · path → arr

Chose
rescue-the-import
Why
Brookline Rentals ran a dry run of 212 units from the rival’s export 26 hours ago and committed nothing; 3 lease dates did not map
From
rescue-the-import · switch-note · founder-note · import-with-them · not-a-fit · not-now
Expects
Collection.imported for lead_63 within 3d
Reopens when
no Collection.imported for lead_63 within 3d
By
worker_2
JSON
{
  "$id": "decisions/decision_355",
  "$type": "Decision",
  "choice": "rescue-the-import",
  "reason": "Brookline Rentals ran a dry run of 212 units from the rival’s export 26 hours ago and committed nothing; 3 lease dates did not map",
  "stakes": "low",
  "options": [
    "rescue-the-import",
    "switch-note",
    "founder-note",
    "import-with-them",
    "not-a-fit",
    "not-now"
  ],
  "by": "worker_2",
  "on": "lead_63",
  "expected": "Collection.imported for lead_63 within 3d",
  "reopensWhen": "no Collection.imported for lead_63 within 3d"
}
NodeReachedHeld backAddedConfidenceBasis
switch-customers4.3 per 100 (4 of 94)0 (0 of 24)+4.3 [−1.6, 10.2]■□□□ Too early to tellheld back at random · n 118 · 22 still maturing · Other Startups’ results +3.9, ■■■□ Likely, n 2,310
new-paying-customers4.3 per 100 (4 of 94)0 (0 of 24)+4.3 [−1.6, 10.2]■□□□ Too early to tellthe same assignment
new-arr ↑$1,986, 4 Customers · Booked $3,278 with 2 rescued imports: credited, not all caused by this Play$0+$1,986 [−$700, +$4,700] · Worth about +$1,500 a month■□□□ Too early to tell (4 vs 0 Customers)the same · 4 trials still deciding
net-new-arr · arr+$1,986$0+$1,986, carried: it moves no other bridge term■□□□ Too early to tellcarried from new-arr

The leaf counts new Customers whose first committed import came from the rival’s export within 30 days of accepting an Offer, per 100 companies assigned, matured 30 days after the first note. It runs to 400 matured in all; at 118 so far the Experiment runs on.

How it moves it: the company sees its own units and leases moved in a dry run (209 of 212 for Brookline) before it commits, and every rival fact it reads cites its source.

Guardrails: treated against held back: trials started per switch Customer, 2.0 against no trials, and opt-outs, 0.9 % of sends; switchers’ 30-day retention is 100 % (6 of 6) against 97 % for other new Customers.

The Campaign card · sample
{
  "$id": "https://api.sb/leaseline.app/campaigns/switch-kit",
  "$type": "Campaign",
  "play": "play:switch-kit@1",
  "path": [
    "switch-customers",
    "new-paying-customers",
    "new-arr",
    "net-new-arr",
    "arr"
  ],
  "experiment": {
    "metric": "switch-customers",
    "unit": "per-100",
    "heldBack": 28,
    "decision": null
  },
  "readout": {
    "value": 4.3,
    "n": 118,
    "maturing": 22,
    "interval": [
      -1.6,
      10.2
    ],
    "readability": "C",
    "basis": "held-back",
    "pooled": false
  },
  "ledger": {
    "spend": 7,
    "workerCost": 5,
    "hours": 3.1,
    "pipelineArr": 1388,
    "wonArr": 3278,
    "projectedArr": 1494,
    "paybackDays": 2
  }
}

Outside benchmark: 38 % of software buyers who switched tools name data migration as the hardest step (a buyer survey of 1,100 companies, 2026-06-30).

Before it · After it. Before it: editorial-seo and paid-search share the rival Triggers and send searchers to the versus page. After it: review-requests hears the switch Customers at day 30, and written-references adds them to the pool; usage-upsell hears them once they pace past Pro.

Example

Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days). 140 companies that publicly run the rival were assigned at random, 112 reached and 28 held back; 118 have matured and 22 are still maturing. Separately, 9 dry-run imports from Visitors to the versus page were abandoned and rescued, outside the assignment. Read every captured thread at ?threads=all; three follow the tables.

What happened, by optionDecisionsReplies or importsWon (credited, not all caused by this Play)
Send the switch note · Founder note86 · 66 positive replies · 3 replies to Dana3 on Pro, 1 on Team (+$1,986 ARR)
Rescue the import94 committed after the rescue1 on Pro, 1 on Team (+$1,292 ARR)
Import with them · Not a fit · Not now3 · 11 · 43 import calls · — · 4 sent after the 48 hours4 trials still deciding
At 40 a month (holdout: 0.2)about 75 in 56 daysheld back at random; 90 days tells +9 or moreOther Startups’ results: +3.9 per 100, ■■■□ Likely, n 2,310
DayEventWhat happenedYour time
0event_119 Campaign.createdthe three steps; dry run fails mailbox-warm15 min
0–2event_133 Change.mergedyou approved 13 rival facts, each with its source25 min
6event_214 Collection.importedthe first import committed after a rescue: Halston Flats, 88 units—
14event_402 Mailbox.warmedthe warm-up ends; the first wave of switch notes sends after your approval20 min
28 · 56event_545 Change.merged · event_998 Readout.createdthe 200-unit founder line: replayed at 7.1 positive per 100, read back at +28d at 6.6, n 315 min
37event_640 Campaign.pausedimporter-test: 2 of 43 fields failed after an export change; fixed and resumed in 5 h30 min
1–56event_131 Approval.created, first of 50you approved the first wave; weekly reviews, from the POST /{namespace}/tasks/next log1.6 h · total 3.1 h measured, 3.2 h asked

Leaseline → Brookline Rentals, Oct 19, 13:46 · Your dry run moved 209 of your 212 units, with every owner and lease.¹ Three lease end dates did not map, and the report shows which.² Fix them in the file and commit, or reply and Dana walks you through it.³ *Signed with the Role’s disclosure line.*⁴

Brookline, Oct 19, 15:10 · fixed the 3 dates and committed. Collection.imported, 212 units; a Pro trial started.

Copperfield Homes, 2026-09-18 · the draft fact-check refused, then the rewrite that sent
- Their price went up 40 % last month, and most teams are leaving.
+ Leaseline imports your units, owners and leases from your current tool’s export, and you see the dry run before anything moves.

Maple & Main, 2026-10-12 10:05 · “The import missed our lease end dates.” · Leaseline, 10:06 · *Thanks. Dana, our founder, handles imports and replies by 17:00 tomorrow.*⁵ The Brief went to Dana (task_519); she held the import call at 14:00 the same day.

¹ Only counts from their own dry run. ² What did not map, from the dry-run report. ³ One next step. ⁴ The fixed signature line, compiled from the Role’s disclosure Setting. ⁵ The routed line, compiled from the Role’s Settings.

Your settings

plays/switch-kit.mdx
---
holdout: 0.2      # 20 % of the companies that run the rival hear nothing, so we can tell
---
Write to companies that publicly run the rival, with the importer’s dry run. At 200 units or more, the note is Dana’s.
State a rival fact only as an approved row says it.
Text
$ npx startup-builder show plays/switch-kit
holdout 0.2 · founder line 200 units · rival facts approved     your file  plays/switch-kit.mdx:2,4,5 (Ask 2026-08-24; line 4 by change_19, 2026-09-21)
family demand · process outbound · worker role:prospector, owns never · chooses 6 (2 rule, 4 read) · sequences switch-outreach-monthly, switch-outreach-monthly-founder, switch-onboarding, dry-run-rescue · checks owned-by-a-person, mailbox-warm, fact-check, source-change, incident-hold · stops 12 (9 on outreach and the importer) · placements 16    catalog  play:switch-kit@1 (Not live yet, gaps #5)

Every key from the catalog

Key Id What it is
Tactic rival-roster Rival roster admits the named rivals worth a switch kit, on searched demand, an export path and a fit verdict a sourced fact supports. Starts at You approve each one.
Tactic rival-watch Rival watch captures each admitted rival’s public Pages daily, diffs them, holds every output a change affects and routes the change. Starts at Sends nothing.
Tactic rival-fact-approval Rival fact approval puts each new or changed claim about a named rival before one human, beside its capture, then releases the outputs citing it. Starts at You approve each one.
Tactic importer-build Importer build maps a rival’s export into the Startup’s Product, tests it against sample exports and launches it with a dated test report. Starts at You approve each one.
Tactic importer-watch Importer watch runs every rival importer against its fixtures nightly and on export changes, and moves a failing one to assisted mode. Starts at It does it, you can stop it.
Tactic switch-offer Switch offer grants a capped switching credit through Stripe to a new Customer whose first import comes from a named rival. Starts at Sends nothing.
Tactic switch-window Switch window turns a rival’s price rise, plan change, incident or backlash into 30 days of dated Pages, a pre-approved Offer, Ads and outreach order. Starts at Sends nothing.
Tactic switch-outreach Switch outreach reaches Organizations that announced they run a named rival, with the importer, the Offer and a dry-run ask, against a 20 percent holdout. Starts at You approve each one.
Tactic compare-endpoint Compare endpoint answers a buying agent’s task with a fit verdict per option from approved facts, and runs an import dry run that commits nothing. Starts at Sends nothing.
Tactic switch-readout Switch readout counts new Customers whose first import came from a named rival, by arm, against the Page split and the outreach holdout. Starts at Sends nothing.
Tactic honest-compare Honest compare builds a comparison Page from captured rival sources and the claims source, with every row the rival wins and a stated method. Starts at You approve each one.
Tactic editorial-publish-gate Editorial publish gate checks the copy rules, claims map, date, byline, duplicates, index rule and batch cap before a publish. 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 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 search-account-build Search account build researches intent phrases and queues a complete, read-only account plan of ad groups, ads, negatives and breakeven bids. Starts at You approve each one.
Tactic incrementality-holdout Incrementality holdout measures the first Transactions a paid arm adds, by a geo holdout or a day switchback, before any scale step. Starts at You approve each one.
Trigger rival-backlash Rival backlash: threshold on Monitor.checked, window 14d.
Trigger rival-user-announced Rival user announced: event on Post.published, window 30d.
Trigger rival-export-change Rival export change: event on Monitor.checked, window 1d.
Trigger importer-test-failed Importer test failed: event on Monitor.checked, window 1d.
Trigger import-dry-run-abandoned Import dry run abandoned: state on File.uploaded, window 72h.
Trigger first-rival-import First rival import: event on Collection.imported, window 30d.
Trigger rival-alternative-named Rival alternative named: threshold on Monitor.checked, window 90d.
Trigger rival-page-change Rival page change: event on Monitor.checked, window 72h.
Trigger rival-moment Rival moment: event on Organization.announced, window 14d.
Trigger rival-switch-ask Rival switch ask: event on Post.published, window 48h.
Trigger competitor-complaint Competitor complaint: event on Review.published, window 7d.
Trigger technology-change Technology change: event on Monitor.checked, window 30d.
Trigger row-dispute Reading dispute: event on Form.submitted, window 2d.
Trigger pricing-visit Pricing visit: event on Visit.recorded, window 72h.
Sequence switch-outreach-monthly Switch outreach monthly: 4 steps; it ends on Any reply; A bounce; Suppression at any scope; A dry run or a signup; A booked Meeting; A missing Reason.
Sequence switch-outreach-monthly-founder Switch outreach monthly founder: 4 steps; it ends on Any reply; A bounce; Suppression at any scope; A dry run or a signup; A booked Meeting; A missing Reason.
Sequence switch-onboarding Switch onboarding: 6 steps; it ends on Cancel; Opt-out, except the charge reminder and receipts.
Sequence dry-run-rescue Dry run rescue: 2 steps; it ends on A commit; Any reply; Opt-out.
Benchmark switch-self-ranking-list-not-picked Share of AI-overview citations of a self-promotional best-software list that leave its owner out of the recommendation: 69 % (Search Engine Journal analysis of 100 B2B best-software queries, 2026-06-18).
Benchmark switch-comparison-content-influence Influence-score uplift for answer-engine-cited pages that carry comparison content, descriptive: 55 % (arXiv 2604.25707, a preprint on what shapes answer-engine responses, 2026-04-28).

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