Skip to content

Outbound calling

A disclosed voice agent calls back anyone who asks within 60 seconds and answers your number; a person dials every cold call.

Channel
Phone
Run oneMode try
You, to your agent

Hand our latest callback request to the SDR agent and show me what it chose and why. Don’t call.

curl "https://api.sb/leaseline.app/events?where=name%20in%20(%22Consent.granted%22%2C%20%22Channel.called%22%2C%20%22Channel.missed%22%2C%20%22Conversation.labeled%22%2C%20%22Lead.qualified%22%2C%20%22Prospect.created%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_1366"}'

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

const { events: [last] } = await $.Event.list({ where: 'name in ("Consent.granted", "Channel.called", "Channel.missed", "Conversation.labeled", "Lead.qualified", "Prospect.created")', 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 (\"Consent.granted\", \"Channel.called\", \"Channel.missed\", \"Conversation.labeled\", \"Lead.qualified\", \"Prospect.created\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_1366"}' --try   # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"lead_47\""}'   # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"lead_47\""}'   # Not live yet (gaps #7)
{
  "name": "do",
  "arguments": {
    "code": "const { events: [last] } = await $.Event.list({ where: 'name in (\"Consent.granted\", \"Channel.called\", \"Channel.missed\", \"Conversation.labeled\", \"Lead.qualified\", \"Prospect.created\")', 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_547",
    "$type": "Decision",
    "choice": "call-back-now",
    "reason": "the yes names AI-voice calls from Leaseline to the number on the form; 14:06 Central is inside their calling window; 220 units is over the 120-unit line",
    "stakes": "low",
    "options": [
      "call-back-now",
      "a-person-calls",
      "answer-the-call",
      "cold-list",
      "answer-in-writing"
    ],
  …
sample · 4 Not live yetPreview

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

action_3 is the first dial, 41 seconds after the form, opening with the fixed clip. action_4 books on the AE’s calendar only if the caller says yes on the call.

+11.7 per 100 · ■□□□ Too early to tell · n 46 · path → arr

Try another: the callbackChoiceReason
Willow & Main: ticked “call me”, 220 units, 14:06 their timecall-back-nowa yes to AI-voice calls, inside their calling window
Birchway Properties: ticked “call me”, 600 units, on your top-25 Lista-person-callsa top-tier company gets a person on every rung
A call to +1 512 555 0100 at 21:40: “Do you handle HOA renewals?”answer-the-callan inbound call, answered at any hour from your own pages
Ridgeline Rentals: a listed office number, and a leasing job reposted 64 dayscold-lista local company with a live reason: on Dana’s list to dial
Kestrel Rentals: “Can someone call me?” by email, no box tickedanswer-in-writingno yes to an AI-voice call: it answers by email and offers a person

Where it sits: Stranger → Lead → Trialing → Activated → At risk → Churned → Won back · Callback Meeting rate → Deals and trials opened → New Customers ↑ → ARR added each week → ARR

Worth’s formula: about +7.5 Meetings per 100 callbacks (Other Startups’ results; your own read is too early) × 47 a month × 0.80 Meetings to opens × 0.30 opens to paid × $800 first order ARR ≈ +$846. Under First signal: With 1 callback in 10 held back at 47 a month, about 30 weeks to tell a +7.5 lift; 90 days can tell only +22 or more.

sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on Consent.granted · first ring 41s after the form · 10 keys Not live yet

Start it this week

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

# 2 · dry run; below, its 422 on the day-0 Startup (--env test, clock at 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":"outbound-calling"}'

# 3 · keep it: people make the first 50 calls from the brief
curl -X POST https://api.sb/leaseline.app/campaigns \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"outbound-calling"}'

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

# off: every call goes to the person line
curl -X POST https://api.sb/leaseline.app/processes/call-routing/pause \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
422Refusedoutbound-calling can’t start yet: 1 check fails and 2 warn.
CheckFixThen
use-when pass · Not live yet, gaps #8Add the callback box; about 1 in 3 forms tick it at Other Startups.POST /{namespace}/metrics/query
baseline warn · Not live yet, gaps #8n 41: your half becomes the baseline as it runs.POST /{namespace}/metrics/query
inbound-source fail · Not live yet, gaps #8Connect your number and add the callback box to the demo form.POST /{namespace}/integrations/connect
owned-by-a-person pass · Not live yet, gaps #8Nothing to do: their callbacks come to your queue instead.POST /{namespace}/tasks/{task}/assign
customer-collision pass · Not live yet, gaps #1Nothing to do: a Customer’s call goes to their CSM.POST /{namespace}/leads/{lead}/assign
claim passNothing to do: the agent says only what an Offer term grants.POST /{namespace}/conversations/{conversation}/answer
role-unstaffed warn · Not live yet, gaps #8Staff the AE, or leave it: booked calls and transfers come to you.POST /{namespace}/roles/{role}/staff
JSON
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "outbound-calling can’t start yet: 1 check fails and 2 warn.",
  "checks": [
    {
      "check": "use-when",
      "status": "pass",
      "observed": "135 forms a month; 0 offer a callback yet",
      "doc": "/plays/outbound-calling#check-use-when",
      "fix": "Add the callback box; about 1 in 3 forms tick it at Other Startups.",
      "then": "Metric.query"
    },
    {
      "check": "baseline",
      "status": "warn",
      "observed": "callbacks by a person: 9 Meetings from 41, n 41 in 90 days",
      "doc": "/plays/outbound-calling#check-baseline",
      "fix": "n 41: your half becomes the baseline as it runs.",
      "then": "Metric.query"
    },
    {
      "check": "inbound-source",
      "status": "fail",
      "doc": "/plays/outbound-calling#check-inbound-source",
      "fix": "Connect your number and add the callback box to the demo form.",
      "then": "Integration.connect"
    },
    {
      "check": "owned-by-a-person",
      "status": "pass",
      "observed": "31 open Leads have a person owner, you",
      "doc": "/plays/outbound-calling#check-owned-by-a-person",
      "fix": "Nothing to do: their callbacks come to your queue instead.",
      "then": "Task.assign"
    },
    {
      "check": "customer-collision",
      "status": "pass",
      "observed": "6 numbers belong to Customers",
      "doc": "/plays/outbound-calling#check-customer-collision",
      "fix": "Nothing to do: a Customer’s call goes to their CSM.",
      "then": "Lead.assign"
    },
    {
      "check": "claim",
      "status": "pass",
      "observed": "the claims source holds 24 approved answers",
      "doc": "/plays/outbound-calling#check-claim",
      "fix": "Nothing to do: the agent says only what an Offer term grants.",
      "then": "Conversation.answer"
    },
    {
      "check": "role-unstaffed",
      "status": "warn",
      "roles": [
        "sales-lead"
      ],
      "owned": 31,
      "doc": "/plays/outbound-calling#check-role-unstaffed",
      "fix": "Staff the AE, or leave it: booked calls and transfers come to you.",
      "then": "Role.staff"
    }
  ]
}

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

CheckFix (one call)Your timeWait
use-when · baseline · pass: 135 forms a month; warn: n 41 person callbacks (gaps #8)POST /{namespace}/metrics/query on callback-meeting-rate, which fills Before in Your number1 minnone
inbound-source · fail (gaps #8)POST /{namespace}/integrations/connect the number, then PATCH /{namespace}/forms/{id} with the callback box15 minthe number’s registration, often 1 to 3 days
owned-by-a-person · customer-collision · claim · pass: 31 owned by you; 6 Customer numbers; 24 answers (gaps #1, #8)nothing; each runs again before every dial0 minnone
role-unstaffed · warn (gaps #8): 31 of your open Leads have a person owner, youPOST /{namespace}/roles/{role}/staff, or leave it: bookings and transfers come to you0 minnone

Your number · Before 22.0 · Now 31.7 per 100 · Target 30 by Dec 31 · ■□□□ Too early to tell · n 46 matured · Other Startups +7.5, ■■■□ Likely

One local number and voice minutes for about 100 callbacks cost $100 a month; each callback averages 3 minutes, about $1. Contact data costs $0.06 a lookup (POST /{namespace}/contacts/{contact}/enrich). A cold call costs only your time. That comes to $100 a month up to 100 callbacks.

When it starts

WhenTrigger idStarts onWindowHow it gets in
Someone ticks “call me” on your form, naming AI-voice callsvoice-consent-formevent on Consent.granted where purpose = 'ai-voice-call'first ring within 60 s; ignored after 1dyour form posts to POST /{namespace}/consents, then POST /{namespace}/leads
Someone calls your numberinbound-callevent on Channel.called where direction = 'inbound'answered on the first ringPOST /{namespace}/integrations/connect with your number
A call to you ends unanswered, or they ask to be called backdropped-callevent on Channel.missed where answered = false or callbackAsked = true1dthe same number
+ 3 more: a written ask for a call, a strong-fit Lead with no yes to a call, and a cold-call reason (?lanes=all)
One signed delivery · StartupBuilder-Signature
{
  "$id": "https://api.sb/leaseline.app/events/event_1366",
  "$type": "Event",
  "name": "Consent.granted",
  "at": "2026-10-19T19:06:12Z",
  "seq": 1366,
  "data": {
    "consent": {
      "$id": "https://api.sb/leaseline.app/consents/consent_201",
      "purpose": "ai-voice-call",
      "startup": "leaseline.app",
      "number": "+1 512 555 0148"
    },
    "lead": {
      "$id": "https://api.sb/leaseline.app/leads/lead_47",
      "company": "Willow & Main",
      "units": 220
    }
  }
}
Reproduce it in test
# each delivery is signed with the Watch’s secret
curl -X POST https://api.sb/leaseline.app/watches \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"selector":"Consent.granted","url":"https://leaseline.app/hooks/sb"}'
npx startup-builder trigger Consent.granted --like event_1366   # replays the delivery in test; the dial is simulated
npx startup-builder clock advance --by 1h   # runs the third dial if the first two went unanswered

Who runs it

SDR · your agent (role:prospector, worker_2) holds the Key Result and speaks in a designed voice that sounds like no real person. It hands booked calls and transfers to AE · Priya Shah (person) (role:sales-lead, worker_3, staffed on day 9), and top-tier callbacks and every cold call to you (Dana Okafor, person, worker_1): a person dials every cold number. Your number links to Did it work.

OptionRung todayEvidence (passed of n)Moves up atDrops back whenYou can
Call back nowIt does it, you can stop it52 calls; 50 person calls from its brief firststarted on day 21, after the 50; spot-check after 500 calls with no Stop in 30 daysany voice Stop (clip, ai-identity, claims) → You hear each transcript firstPOST /{namespace}/actions/{action}/cancel before the dial · POST /{namespace}/conversations/{conversation}/escalate on the call
Answer the call · Answer in writingIt does it, you can stop it38 calls · 9 of 9the samethe samethe same
A person calls back · Put it on your cold listA person, every time6 held back, 5 top tier · 34 dials from 12 briefsnever: a person dials—POST /{namespace}/tasks/{task}/claim · POST /{namespace}/tasks/{task}/return
Change the calling ruleYou approve each one1 merged of 1never—POST /{namespace}/changes/{change}/merge · POST /{namespace}/changes/{change}/revert
Never: a call with no yes to AI voice, a cloned voice, a discountRefused, every time3 strong-fit Leads with no yes: written answersnever—none: the Check holds it

What it reads · What it may call. It reads the form, the yes and its scope, the claims source and the calendar. A pricing-page visit may order the cold list and is never spoken on a call; nothing a caller says picks a number to dial. Its tools are POST /{namespace}/conversations, POST /{namespace}/meetings/book, POST /{namespace}/tasks/{task}/assign, POST /{namespace}/conversations/{conversation}/answer, POST /{namespace}/messages/{message}/schedule, and any other call becomes an Approval under the Role’s Mandate.

The brief (folded): compiled Agent-register Markdown · play:outbound-calling@1 · each line names its record key
You hold the SDR Key Result: 30 Meetings held per 100 callbacks by 2026-12-31.   ← worker, metric
On each callback ask or call, choose one option and say why.                     ← triggers, chooses
Play the opening clip first and in full; say you are an agent if asked.          ← copyRules 1, 3
One ask per call; the list Price only as the Pricing Page shows it.              ← copyRules 6, 9
Over 120 units, book the AE.                                                     ← your file, line 5
“Person” at any point reaches a person, live or booked.                          ← copyRules 12

The slow loop, as a Change. decision_366 (2026-09-28, weekly) read 11 callbacks that rang once and went to voicemail, with 1 Meeting, and 9 that rang twice in a row, with 4. change_27 made the double ring the first attempt for every callback (your file, line 6), and Dana merged it with POST /{namespace}/changes/{change}/merge. Replayed on past callbacks, POST /{namespace}/changes/{change}/review put the agent half at 34 per 100; read back at +14d it is 33.3, n 18, ■□□□ Too early to tell (Example, day 50).

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 outbound-calling-58 --agent "claude -p" --seat prospector   # seated: 31.7 per 100, 0 clip misses, $1.02 a call · yours: 28.1, 0 misses → You approve each one

What it can do next

OptionPicked whenDecided byRunsHands toIf it’s just you
Call back now call-back-nowa yes to AI-voice calls, inside their calling windowYour rule (line 5)callback-attempts: double ring at once, then 1 h, then 1 day → POST /{namespace}/conversations → on yes POST /{namespace}/meetings/bookover 120 units: POST /{namespace}/tasks/{task}/assign → AE · Priya Shah, due 60 min, Brief attached · Take this oneyou, with the Brief, within 60 min
A person calls back a-person-callsa top-tier company, a strong fit with no yes to AI voice, or held backYour rule (line 7)human-callback: a person calls from the brief within 5 min in staffed hours, twice at most → POST /{namespace}/tasks/{task}/assignyou · Take this oneyou
Answer the call answer-the-callsomeone calls your numberAgent’s call, with its reasonroute-call → POST /{namespace}/conversations/{conversation}/answer from the claims sourcea transfer: AE · Priya Shah, live, or booked within 60 minyou
Put it on your cold list cold-lista local company with a live reason and a listed numberAgent’s call, with its reasoncold-call-touches: the brief and talk track → POST /{namespace}/tasks/{task}/assign; a person dials 3 times in 7 daysyou dial · Take this oneyou
Answer in writing answer-in-writingoutside the calling window, or a written ask with no yes to a callAgent’s call, with its reasonanswer-inbound → POST /{namespace}/messages/{message}/schedule, offering a person’s callnobodythe same
When they write backFixture replyWhat happensWho answersBy whenCall
Interested“Yes, Tuesday at 3 works.” (on the call)books it on the AE’s calendar while they listen; the Brief goes to the AE (When it hands to you)AE · Priya Shah (person)60 minPOST /{namespace}/meetings/book
Send me pricing“Just email me the pricing.”the call ends; the Pricing Page’s list Price arrives by email with one next stepSDR · your agent5 minPOST /{namespace}/messages/{message}/schedule
Not now“Call me after the holidays.”no more dials; it moves to Nurture with January notedSDR · your agentat oncePOST /{namespace}/leads/{lead}/nurture
Wrong person“You want our ops manager, Sam.”it asks for Sam’s email to send the summary; it never dials a number it heard on the callSDR · your agent1 business dayPOST /{namespace}/conversations/{conversation}/answer
Out of officeno answer twice; voicemail on the second ringone fixed voicemail clip, then the 1 h and 1-day attemptsnobody1 dayPOST /{namespace}/messages/{message}/schedule
Question it can’t answer“Can you migrate our 6 years of lease history?”a live transfer to the AE, or a booked call with the question in the BriefAE · Priya Shah (person)60 minPOST /{namespace}/conversations/{conversation}/escalate
Stop“Don’t call this number again.”the number goes on your do-not-call list; nothing dials it on any lanenobodyat oncePOST /{namespace}/contacts/{contact}/suppress

When it hands to you

Booked calls and transfers go to AE · Priya Shah (person); top-tier callbacks and every cold call come to you, and before day 9 every hand-off 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_455",
    "$type": "Task",
    "assignee": "worker_3",
    "due": "2026-10-19T20:07:00Z",
    "on": "lead_47",
    "brief": {
      "who": "Jordan, Willow & Main (220 units)",
      "said": "Renewals across 3 buildings, and owners want monthly statements.",
      "chose": "call-back-now",
      "why": "a yes to AI voice; over the 120-unit line",
      "booked": "Tue 15:00 Central",
      "call": "transcript and recording, 3 min 40 s",
      "never": [
        "a price beyond the list",
        "a discount",
        "terms"
      ]
    }
  },
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_418",
    "$type": "Task",
    "assignee": "worker_1",
    "due": "2026-10-06T14:00:00Z",
    "on": "prospect_44",
    "brief": {
      "who": "Ridgeline Rentals, office line +1 512 555 0172",
      "reason": "leasing coordinator job reposted after 64 days",
      "chose": "cold-list",
      "why": "a local company with a live reason",
      "track": "name, Startup, reason in the first 15 seconds; one ask: a 20-minute call"
    }
  }
]

Each 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 dials that Lead no more and every call or 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 (AE to you), and person-promise fires.

On time: AE · Priya Shah, 9 of 9 booked calls claimed within 60 min, median 12 min, n 9, ■□□□ Too early to tell beside Other Startups’ 95 % on time; you, 12 cold briefs dialed within their day, n 12. Your queue: counted by kind: in weeks 1 to 3, 50 person callbacks from the brief; after day 21, about 2 transfers, 3 cold briefs and 5 transcripts to spot-check 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 dial to a Lead or open Deal a person owns, or a Conversation a person claimedLeave it to the person who owns it: the draft waits in their queue.POST /{namespace}/tasks/{task}/assign
voice-consent (the record’s Consent gate)any AI-voice dial with no recorded yes naming AI-voice calls, the Startup and the numberCall back in person, or answer in writing; no override exists.POST /{namespace}/tasks/{task}/assign
do-not-call (the record’s Do-not-call gate)a number on Suppression, your list, or a Market list older than 31 daysLeave the number off; refresh the Market list if it is over 31 days old.GET /{namespace}/suppressions
quiet-hours (the record’s Quiet hours gate)a dial outside the calling window in the called party’s local timeWait for their window; the narrower of two time zones wins.POST /{namespace}/messages/{message}/schedule
customer-collision (gaps #1)a new-lead callback to a number on an active SubscriptionSend it to their CSM instead: the Contact leaves the List.POST /{namespace}/leads/{lead}/assign
claim (hub, on POST /{namespace}/conversations/{conversation}/answer)a term with no accepted terms Version, or money back no Action keepsCut the line, or cite the Offer term that grants it.POST /{namespace}/conversations/{conversation}/answer
Stop (pauses the Play)Pauses whenThen
clipone call log lacks the opening clip’s hashAI calls pause on every number until you review it
ai-identityone transcript says or implies the agent is a personevery voice Tactic pauses; the Marketing Lead is told
claimsone transcript sentence states a price, policy or promise outside the claims sourcecallbacks drop to You hear each transcript first
complaintone “I never asked for this call” opens an audit of that yes; two in 200 callsAI callbacks pause on that form
spam-labela carrier labels the number as spamoutbound calls stop; an Issue opens; calls resume on a clean read
early-hang-upover 40 % of answered callbacks end within 10 s of the clip, after 50the Tactic pauses for review
meeting-floorthe agent half books fewer Meetings per 100 than people, after 100 calls eachcallbacks return to people, with the agent’s brief
kill-switchyou press itPOST /{namespace}/processes/{process}/pause on call-routing; every call goes to the person line
cold-refusaldo-not-call asks over 5 % of cold connects, after 100; one dial to a suppressed numberthat cold list pauses; a suppressed dial trips kill-switch
cold-floorzero Meetings after 100 cold connectscold calls end for that List until the Offer or the reason changes

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

422RefusedThe follow-up email to Harbor Row did not send: it promises a free month no Offer term grants.
CheckFixThen
claim failCut the line, or cite the Offer term that grants it.POST /{namespace}/conversations/{conversation}/answer
owned-by-a-person pass · Not live yet, gaps #8—POST /{namespace}/tasks/{task}/assign
JSON
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The follow-up email to Harbor Row did not send: it promises a free month no Offer term grants.",
  "checks": [
    {
      "check": "claim",
      "status": "fail",
      "doc": "/plays/outbound-calling#check-claim",
      "fix": "Cut the line, or cite the Offer term that grants it.",
      "then": "Conversation.answer"
    },
    {
      "check": "owned-by-a-person",
      "status": "pass"
    }
  ]
}

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

The Stops that fired, as Campaign.paused reasons · sample
[
  {
    "reason": {
      "limit": "spam-label",
      "observed": "“Spam risk” on 1 carrier",
      "bound": "0",
      "window": "the daily number read, Oct 3"
    },
    "doc": "/plays/outbound-calling#stop-spam-label",
    "task": "task_431"
  }
]

Did it work

One callback in 10, drawn when the form lands, is held back at random and gets a person’s callback in staffed hours; both halves are read from that assignment (rules m–o), counting from day 21, when the agent started dialing. 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).

+11.7 per 100 · ■□□□ Too early to tell · n 46 · path → arr

Chose
call-back-now
Why
the yes names AI-voice calls from Leaseline to the number on the form; 14:06 Central is inside their calling window; 220 units is over the 120-unit line
From
call-back-now · a-person-calls · answer-the-call · cold-list · answer-in-writing
Expects
a Meeting held by day 14
Reopens when
no Meeting.booked for lead_47 after the 4th dial, or within 14d
By
worker_2
JSON
{
  "$id": "decisions/decision_547",
  "$type": "Decision",
  "choice": "call-back-now",
  "reason": "the yes names AI-voice calls from Leaseline to the number on the form; 14:06 Central is inside their calling window; 220 units is over the 120-unit line",
  "stakes": "low",
  "options": [
    "call-back-now",
    "a-person-calls",
    "answer-the-call",
    "cold-list",
    "answer-in-writing"
  ],
  "by": "worker_2",
  "on": "lead_47",
  "expected": "a Meeting held by day 14",
  "reopensWhen": "no Meeting.booked for lead_47 after the 4th dial, or within 14d"
}
NodeAgent halfYour half (a person calls back)AddedConfidenceBasis
callback-meeting-rate31.7 per 100 (13 of 41)20.0 (1 of 5)+11.7 [−25.4, +48.8]■□□□ Too early to tellheld back at random, 1 in 10 · n 46 · 12 still maturing · Other Startups +7.5, ■■■□ Likely, n 6,900
opened-deals-and-trials26.8 per 100 (11 of 41)20.0 (1 of 5)+6.8 [−29.9, +43.5] · Not carrying up yet■□□□ Too early to tellthe same assignment
new-arr ↑$3,087, 3 Customers · Booked: credited, not all caused by this Play$347, 1 Customer+$2,740 [−$1,600, +$7,000] · Worth about +$850 a month■□□□ Too early to tell (3 vs 1 Customers)the same · 5 trials still deciding
net-new-arr · arr+$3,087+$347+$2,740, carried: it moves no other bridge term■□□□ Too early to tellcarried from new-arr

The leaf counts Meetings held within 14 days per 100 callbacks placed, wrong numbers kept in the denominator (rule m), to 100 matured calls in each half; at 41 and 5 the Experiment runs on.

How it moves it: a median first ring 41 seconds after the form for the agent half, against 2 h 50 min for a person’s callback (time-to-first-call on callbacks).

Guardrails: treated against held back: Deals or trials opened per Meeting held, 0.85 against 1.00 (meeting-to-open-rate, Not live yet), and “I never asked for this call”, 0 of 52 against a Stop at 2 in 200.

The Campaign card · sample
{
  "$id": "https://api.sb/leaseline.app/campaigns/outbound-calling",
  "$type": "Campaign",
  "play": "play:outbound-calling@1",
  "path": [
    "callback-meeting-rate",
    "opened-deals-and-trials",
    "new-arr",
    "net-new-arr",
    "arr"
  ],
  "experiment": {
    "metric": "callback-meeting-rate",
    "unit": "per-100",
    "heldBack": 6,
    "decision": null
  },
  "readout": {
    "value": 11.7,
    "n": 46,
    "maturing": 12,
    "interval": [
      -25.4,
      48.8
    ],
    "readability": "inadmissible",
    "basis": "held-back",
    "pooled": false
  },
  "ledger": {
    "spend": 186,
    "workerCost": 6,
    "hours": 9.4,
    "pipelineArr": 4800,
    "wonArr": 3087,
    "projectedArr": 846,
    "paybackDays": 22
  }
}

Outside benchmark: 4 % of business demo requests got a phone call at all, after a median 34.5 hours (a vendor-run speed-to-lead field experiment, 2026-09-22).

Before it · After it. Before it: paid-search, paid-social, directory-listings, free-tools and lead-nurture end on a form, a number or a call-me reply. After it: booked calls reach the AE as instant-response hand-offs do, partial fits enter lead-nurture, and every call outcome maps to a reply kind.

Example

Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days). 88 forms ticked “call me”: people made the first 50 callbacks from the brief, then from day 21, 58 were assigned at random, 52 to the agent and 6 held back; 46 have matured. The agent also answered 38 calls to the number. Read every captured thread at ?threads=all; three follow the tables.

What happened, by optionCallsMeetings heldWon (credited, not all caused by this Play)
Call back now52 (148 dials)16, 13 of the 41 matured1 on the $2,400 plan (+$2,393 ARR), 1 on Pro (+$347 ARR)
Answer the call3841 on Pro (+$347 ARR)
A person calls back · Put it on your cold list · Answer in writing11 · 34 dials from 12 briefs · 92 · 2 · 11 on Pro (+$347 ARR) · 0 · 0
Agent half · held back52 · 616 · 1$3,087 · $347
At 20 callback asks a month (the Use it when volume)about 37 in 56 daysheld back 1 in 10: Too early to tell at 90 daysOther Startups’ results: +7.5 per 100, ■■■□ Likely, n 6,900
DayEventWhat happenedYour time
0event_118 Campaign.createdthe three steps; the dry run fails inbound-source25 min
2event_131 Integration.connectedthe number registered; the callback box went live on the demo form15 min
2event_140 Meeting.bookedthe first Meeting on a person’s callback from the brief—
9event_301 Role.staffedAE · Priya Shah (person) takes role:sales-lead—
0–20event_133 Task.claimed, first of 50you and Priya made the first 50 callbacks from the agent’s brief4 h 10 min
21event_455 Role.updatedthe voice agent starts dialing consented callbacks—
36 · 50event_655 Change.merged · event_861 Readout.createddouble ring first: replayed at 34 per 100, read back at 33.3, n 185 min
40event_702 Campaign.pausedspam-label on 1 carrier; calls resumed on a clean read 19 h later20 min
21–56event_480 Conversation.created, first of 90you spot-checked transcripts and dialed 34 cold calls; weekly reviews, from the POST /{namespace}/tasks/next log5 h · total 9.4 h measured, 9 h asked

Willow & Main, form at 14:05:31 Central · ticked “call me”, 220 units.

Leaseline (voice agent), 14:06:12 · *The fixed opening clip, naming Leaseline, the AI voice and the recording.*¹ “You asked about renewals across your buildings. Is now still a good time?”² Jordan named 3 buildings and monthly owner statements; the agent offered the AE’s times³ and booked Tue 15:00 with POST /{namespace}/meetings/book at 14:09:52. The Brief reached Priya at 14:10.

Harbor Row, 2026-09-30 · the follow-up claim refused, then the rewrite that sent
- As promised on the call, your first month is free.
+ As we said on the call, Pro is $29 a month, and the trial is free for 14 days.

A call to +1 512 555 0100, 2026-10-08 10:12 · “Can you migrate our 6 years of lease history?” · Leaseline (voice agent) · *Priya, our account lead, calls you back by 11:12.*⁴ The Brief went to Priya (task_437); she claimed it at 10:20 and called back at 10:48.

¹ The fixed opening clip, compiled from the Role’s Settings and never typed. ² It quotes the form; nothing they did not give is spoken. ³ One ask, with times the calendar shows. ⁴ The routed line, compiled from the Role’s Settings.

Your settings

plays/outbound-calling.mdx
---
holdout: 0.1      # 1 callback in 10 gets a person instead
number: +1 512 555 0100
---
Over 120 units, book our AE.
Every callback starts with the double ring.
Our top 25 get a person on every rung, and I dial every cold call.
Text
$ npx startup-builder show plays/outbound-calling
holdout 0.1 · number +1 512 555 0100 · size line 120 units · double ring · top 25 to a person     your file  plays/outbound-calling.mdx:2,3,5,6,7 (Ask 2026-08-24; line 6 by change_27, 2026-09-28)
family outreach · process call-routing · worker role:prospector, owns never · chooses 5 (2 rule, 3 read) · sequences callback-attempts, human-callback, cold-call-touches · approval every-send · checks owned-by-a-person, voice-consent, do-not-call, quiet-hours, customer-collision, claim · stops 10    catalog  play:outbound-calling@1 (Not live yet, gaps #5)

Every key from the catalog

KeyIdWhat it is
Tacticconsented-callbackConsented callback: A disclosed voice agent calls back a Form submitter whose Consent names AI-voice calls within 60 seconds, qualifies them and books or transfers. Starts at You approve each one.
Tacticinbound-answerInbound answer: A disclosed voice agent answers every call to a Startup number at any hour, from the claims source, and routes, books or hands off. Starts at You approve each one.
Tactichuman-call-prepHuman call prep: The agent builds the cold call list, the briefs and the talk tracks, and coaches after each call; a Human dials every number by hand. Starts at You approve each one.
Tacticcall-gateCall gate: The send gate for the phone medium: Suppression, do-not-call lists, Consent for AI voice, calling windows, Market stamp and number health, before every dial. Starts at Sends nothing.
Tacticsend-gate-checkSend 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.
Tacticreply-matrixReply 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.
Triggervoice-consent-formVoice consent form: event on Consent.granted, window 1d.
Triggerinbound-callInbound call: event on Channel.called, window 1h.
Triggerdropped-callDropped call: event on Channel.missed, window 1d.
Triggercall-me-replyCall me reply: event on Conversation.labeled, window 1d.
Triggerstrong-fit-leadStrong fit lead: event on Lead.qualified, window 1d.
Triggercold-call-reasonCold call reason: event on Prospect.created, window 7d.
Sequencecallback-attemptsCallback attempts: 4 steps; it ends on A connected call; A booked Meeting; Any reply to the written answer; An opt-out or Suppression; Consent withdrawn.
Sequencehuman-callbackHuman callback: 2 steps; it ends on A connected call; A booked Meeting; Any reply to the written answer; An opt-out or Suppression.
Sequencecold-call-touchesCold call touches: 3 steps; it ends on A connected call; A voicemail left; A do-not-call request; Any reply; A booked Meeting; The Reason expiring.
Benchmarkdemo-request-call-coverageShare of B2B demo requests that got any phone call: 4 % (A vendor-run speed-to-lead field experiment, 2026-09-22).
Benchmarkdemo-request-hours-to-callAverage hours from a demo request to the first call: 34.5 hours (A vendor-run speed-to-lead field experiment, 2026-09-22).

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