Account ads
Shows ads to fitting companies on your list a week before the first email, then counts the Deals they open against companies held back.
- Channel
- Ads and sponsorships
Be our Marketing Lead for ads: show LinkedIn ads to property managers on our List with 50 to 500 units, starting 9 days before each first cold email. Hold 30 % back so we can tell. $1,000 a month. Show me what’s missing; don’t spend.
# Not live yet (gaps #2)
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":"account-ads"}'import { $ } from 'startup-builder'
return await $.Campaign.create({ play: 'account-ads' }, { mode: 'try' }) // dry run on the day-0 Startup, 2026-08-24 · Not live yet, gaps #2
npx startup-builder do Campaign.create --json '{"play":"account-ads"}' --try # Not live yet (gaps #2){
"name": "do",
"arguments": {
"code": "return await $.Campaign.create({ play: 'account-ads' }, { mode: 'try' }) // dry run on the day-0 Startup, 2026-08-24 · Not live yet, gaps #2",
"mode": "try"
}
}application/json{
"type": "https: //startupbuilder.dev/ errors/ refused",
"title": "Refused",
"status": 422,
"detail": "account-ads can’t start yet: 2 checks fail and 1 warns.",
"checks": [
{
"check": "owned-by-a-person",
"status": "pass",
"observed": "0 listed companies have an open Deal a person owns; 4 Customers left the List",
"doc": "/plays/ account-ads#check-owned-by-a-person",
"then": "Task.assign"
},
{
…
Hand our latest engaged company to the Marketing Lead agent and show me what it chose and why. Don’t spend.
curl "https://api.sb/leaseline.app/events?where=name%20in%20(%22Organization.listed%22%2C%20%22Message.scheduled%22%2C%20%22Visit.recorded%22%2C%20%22Visitor.identified%22%2C%20%22Ad.clicked%22%2C%20%22Lead.qualified%22%2C%20%22Deal.created%22%2C%20%22Organization.delisted%22%2C%20%22Post.published%22%2C%20%22Audience.resized%22%2C%20%22Consent.withdrawn%22%2C%20%22Monitor.checked%22%2C%20%22Experiment.matured%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_1398"}'
# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/decisions?where=on%20%3D%20%22organization_88%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%22organization_88%22" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"import { $ } from 'startup-builder'
const { events: [last] } = await $.Event.list({ where: 'name in ("Organization.listed", "Message.scheduled", "Visit.recorded", "Visitor.identified", "Ad.clicked", "Lead.qualified", "Deal.created", "Organization.delisted", "Post.published", "Audience.resized", "Consent.withdrawn", "Monitor.checked", "Experiment.matured")', 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 (\"Organization.listed\", \"Message.scheduled\", \"Visit.recorded\", \"Visitor.identified\", \"Ad.clicked\", \"Lead.qualified\", \"Deal.created\", \"Organization.delisted\", \"Post.published\", \"Audience.resized\", \"Consent.withdrawn\", \"Monitor.checked\", \"Experiment.matured\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_1398"}' --try # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"organization_88\""}' # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"organization_88\""}' # Not live yet (gaps #7){
"name": "do",
"arguments": {
"code": "const { events: [last] } = await $.Event.list({ where: 'name in (\"Organization.listed\", \"Message.scheduled\", \"Visit.recorded\", \"Visitor.identified\", \"Ad.clicked\", \"Lead.qualified\", \"Deal.created\", \"Organization.delisted\", \"Post.published\", \"Audience.resized\", \"Consent.withdrawn\", \"Monitor.checked\", \"Experiment.matured\")', 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"
}
}application/json{
"decision": {
"$id": "https: //api.sb/ leaseline.app/ decisions/ decision_418",
"$type": "Decision",
"choice": "proof-ads",
"reason": "Cedar Row Management clicked the problem ads 4 times in 9 days and opened no lead form",
"stakes": "low",
"options": [
"problem-ads",
"proof-ads",
"offer-ads",
"name-the-company",
"sponsor-your-post",
"to-the-ae",
…
sample · 4 Not live yetPreviewOn PATCH /{namespace}/campaigns/{id}Off PATCH /{namespace}/campaigns/{id}Take this one POST /{namespace}/tasks/{task}/claim
action_2 moves Cedar Row from the problem company list to the proof list at the next 06:00 upload. action_3 is the proof change set, which lands paused until you approve it (You approve each one).
+0.32 Deals per 100 listed · ■□□□ Too early to tell · n 743 matured · path → arr
| Try another: the listed company | Choice | Reason |
|---|---|---|
| Cedar Row Management: 4 clicks on the problem ads in 9 days | proof-ads | engaged, and no lead-form open yet |
| Harlow Lettings: listed from the cold-email cell of Oct 20 | problem-ads | newly listed, no engagement |
| Birchway Properties: a demo request from Rosa, after the offer ads | to-the-ae | a written request at a listed company |
| Juniper Homes: took Leaseline Pro on Oct 14 | off-the-list | a Customer leaves every company list |
- 60 min week 1, then 1 h a week
- Your time
Where it sits: Stranger → Lead → Trialing → Activated → At risk → Churned → Won back · Account Deal rate → Deals and trials opened → New Customers ↑ → ARR added each week → ARR
Worth’s formula: about +0.51 Deals and trials opened per 100 listed companies a month × 751 treated × 0.29 opens to paid × $1,669 first order ARR ≈ +$1,850. Under First signal: About 5 months to tell a +0.4 lift at 750 listed (90 days to read, 2 reads to pool); at 350 listed, 180 days can tell only +1.1 or more, so it reads beside Other Startups’ results.
sample · api.sb/
Start it this week
npx startup-builder add play account-ads # 1 · writes plays/account-ads.mdx (Not live yet, gaps #5)
# 2 · dry run; below, its 422 on the day-0 Startup (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":"account-ads"}'
# 3 · keep it: every change set lands paused, 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":"account-ads"}'
# on
curl -X PATCH https://api.sb/leaseline.app/campaigns/account-ads \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"endsAt":null}'
# off: every ad set pauses through the ad proxy
curl -X PATCH https://api.sb/leaseline.app/campaigns/account-ads \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"endsAt":"now"}' | Check | Fix | Then |
|---|---|---|
owned-by-a-person pass · Not live yet, gaps #8 | 0 listed companies have an open Deal a person owns; 4 Customers left the List | POST /{namespace}/tasks/{task}/assign |
use-when pass · Not live yet, gaps #8 | 1,083 fitting companies; cold-email live; $1,000 a month Limit | POST /{namespace}/metrics/query |
baseline pass · Not live yet, gaps #8 | 0.31 Deals opened per 100 listed companies in 90 days, n 1,083 | POST /{namespace}/metrics/query |
conversion-send fail | Connect LinkedIn ads and send one test qualified Lead; that Venue stays off until it lands. | POST /{namespace}/integrations/connect |
approved-pages fail · Not live yet, gaps #8 | Publish one Customer result Page; proof ads quote only your own pages. | POST /{namespace}/sites/{site}/publish |
role-unstaffed warn · Not live yet, gaps #8 | Staff the AE, or leave it: qualified Leads come to you. | POST /{namespace}/roles/{role}/staff |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "account-ads can’t start yet: 2 checks fail and 1 warns.",
"checks": [
{
"check": "owned-by-a-person",
"status": "pass",
"observed": "0 listed companies have an open Deal a person owns; 4 Customers left the List",
"doc": "/plays/account-ads#check-owned-by-a-person",
"then": "Task.assign"
},
{
"check": "use-when",
"status": "pass",
"observed": "1,083 fitting companies; cold-email live; $1,000 a month Limit",
"doc": "/plays/account-ads#check-use-when",
"then": "Metric.query"
},
{
"check": "baseline",
"status": "pass",
"observed": "0.31 Deals opened per 100 listed companies in 90 days, n 1,083",
"doc": "/plays/account-ads#check-baseline",
"then": "Metric.query"
},
{
"check": "conversion-send",
"status": "fail",
"doc": "/plays/account-ads#check-conversion-send",
"fix": "Connect LinkedIn ads and send one test qualified Lead; that Venue stays off until it lands.",
"then": "Integration.connect"
},
{
"check": "approved-pages",
"status": "fail",
"doc": "/plays/account-ads#check-approved-pages",
"fix": "Publish one Customer result Page; proof ads quote only your own pages.",
"then": "Site.publish"
},
{
"check": "role-unstaffed",
"status": "warn",
"roles": [
"sales-lead"
],
"owned": 0,
"doc": "/plays/account-ads#check-role-unstaffed",
"fix": "Staff the AE, or leave it: qualified Leads come to you.",
"then": "Role.staff"
}
]
}It says no, says why, and names the fix.
| Check | Fix (one call) | Your time | Wait |
|---|---|---|---|
owned-by-a-person · use-when · baseline · pass: 0 person-owned Deals on the List; 1,083 fitting companies; 0.31 Deals per 100, n 1,083 (gaps #8) | POST /{namespace}/metrics/query on account-deal-rate, which fills Before in Your number | 1 min | none |
conversion-send · fail (Not live yet) | POST /{namespace}/integrations/connect with venue linkedin, then one test qualified Lead from your records | 10 min | the test conversion, under 24 h |
approved-pages · fail: no Customer result Page (Not live yet) | POST /{namespace}/sites/{site}/publish the Page; until then problem and offer ads run, proof stays off | 20 min | none |
role-unstaffed · warn (gaps #8): no AE, so qualified Leads come to you | POST /{namespace}/roles/{role}/staff, or leave it | 0 min | none |
Your number · Before 0.31 Deals per 100 listed · Now 0.77 per 100 treated · Target 1.0 by Dec 31 · ■□□□ Too early to tell · n 743 matured · held back at random
Media is your $1,000 a month on LinkedIn, held by a Limit: $800 to the company lists and $200 to sponsoring your own posts. Contact and company data cost $0.02 per listed company (POST /{namespace}/contacts/{contact}/enrich) and each drafted concept costs $0.30 (Worker cost). That comes to about $1,030 a month at 750 listed, $30 of it not media.
When it starts
| When | Trigger id | Starts on | Window | How it gets in |
|---|---|---|---|---|
| A fitting company clears your List rule | organization-listed | state on Organization.listed where fit = 'strong' and not a Customer, open Deal or Suppression | ads start within 1 d; ignored after 14 d | PATCH /{namespace}/audiences/{id} on your named List, or a sending Play’s cell |
| A sending Play schedules a first email 9+ days out | air-cover-requested | event on Message.scheduled where step = 1 and sendAt >= now + 9d | joins the company list within 1 h | the sending Play’s POST /{namespace}/messages/{message}/schedule |
| Someone at a listed company becomes a qualified Lead | qualified-lead-recorded | event on Lead.qualified where organization on the named List | reaches the AE within 60 min; the Venue within 24 h | POST /{namespace}/leads/{lead}/qualify in your records |
+ 10 more for pricing visits, surges, ad engagement, Deals, exits, your posts, list size, opt-outs, drift and holdout reads (?lanes=) |
{
"$id": "https://api.sb/leaseline.app/events/event_1398" ,
"$type": "Event",
"name": "Ad.clicked",
"at": "2026-10-19T06:00:41Z" ,
"seq": 1398,
"data": {
"organization": {
"$id": "https://api.sb/leaseline.app/organizations/organization_88" ,
"name": "Cedar Row Management",
"list": "treated"
},
"clicks": 4,
"leadFormOpens": 0,
"window": "14d",
"adSet": "problem"
}
}# 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":"Ad.clicked","url":"https://leaseline.app/hooks/sb"}'
npx startup-builder trigger Ad.clicked --like event_1398 # replays the company report row
npx startup-builder clock advance --by 1d # runs the 06:00 upload that moves Cedar Row (action_2) Who runs it
Marketing Lead · your agent (role:, worker_6) holds the Key Result and runs the ad sets as Paid media (role:). It hands qualified Leads to AE · Priya Shah (person) (role:, worker_3, staffed on day 9), and every company-name ad and sponsored post to you (Dana Okafor, person, worker_1). Your number links to Did it work.
| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
|---|---|---|---|---|---|
| Start on problem ads · Move to proof ads · Show the offer | You approve each one | 26 of 27 concepts approved unedited, in 3 change sets | at 36 approved unedited across 4 change sets and no Stop in 30 days: about day 75 | any Stop drops one rung; a drift twice in 7 days → every change waits for you | POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse |
| To the AE · Take them off the List | It does it | 14 of 14 · 31 of 31 | — | a person-owned company gets an ad change → You approve each one | POST /{namespace}/actions/{action}/redirect |
| Name the company · Sponsor your post | You approve each one, on every rung | 3 of 4 · 2 of 2 | never | — | POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse |
| Change the List rule or the refresh | You approve each one | 1 merged of 1 | never | — | POST /{namespace}/changes/{change}/merge · POST /{namespace}/changes/{change}/revert |
| Never: a Visit, page or click in an ad; a Price your Pricing Page lacks; a rival named without your OK | To a person, every time | 1 asked, 1 handed over | never | — | POST /{namespace}/approvals/{approval}/approve |
What it reads · What it may call. A Visit, a page read or an ad click chooses which company is listed and which ad set it sees; none ever appears in an ad, a Page or a Message. It writes only through the ad proxy, and the holdout list is excluded from every Campaign you run. Its tools are PATCH /{namespace}/audiences/{id}, POST /{namespace}/ads, PATCH /{namespace}/ads/{id}, POST /{namespace}/content/draft, POST /{namespace}/leads/{lead}/assign, POST /{namespace}/tasks/{task}/assign, POST /{namespace}/suppressions, and any other call becomes an Approval under the Role’s Mandate.
You hold the Marketing Lead Key Result: 1.0 Deals opened per 100 listed companies by 2026-12-31. ← worker, metric
On each listing, click report or Lead, choose one option and say why. ← triggers, chooses
Start ads 9 days before the first cold email to that company. ← your file, line 6
Every sentence maps to an approved Page or the company’s own public source. ← copyRules 1
Never name a Visit, a page, a click or a Person in an ad. ← copyRules 3, 5
Never change an ad set for a company whose open Deal a person owns. ← worker.owns, owned-by-a-person The slow loop, as a Change. decision_431 (2026-10-05, weekly) read proof concepts falling under their ad set’s median by day 14, so change_22 moved the refresh from 21 to 14 days (your file, line 7) and Dana merged it with POST /{namespace}/changes/{change}/merge. Replayed on past weeks, POST /{namespace}/changes/{change}/review put qualified Leads per 100 treated at 2.1; read back at +14d it is 1.9, n 751, ■□□□ Too early to tell (Example, day 42).
Bring your own agent (folded) starts at You approve each one when it matches the seated agent on the eval suite; ad change sets never pass that rung for any agent. POST /{namespace}/roles/{role}/staff seats it.
npx startup-builder eval --scenario account-ads-1073 --agent "claude -p" --seat marketing-lead # seated: 1.86 qualified Leads per 100, 0 Checks failed served, $131 per qualified Lead · yours: 1.71, 1 failed, $144 What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
|---|---|---|---|---|---|
Start on problem ads problem-ads | newly listed, no engagement | Your rule (line 5) | PATCH /{namespace}/audiences/{id} adds it to the problem list → POST /{namespace}/ads for the ad set’s concepts | nobody | the same |
Move to proof ads proof-ads | 3+ clicks in 14 days, no lead-form open | Agent’s call, with its reason | PATCH /{namespace}/audiences/{id} moves it → PATCH /{namespace}/ads/{id} on the proof set | you approve the change set | you |
Show the offer offer-ads | engaged with proof, or read pricing | Agent’s call, with its reason | PATCH /{namespace}/audiences/{id} → PATCH /{namespace}/ads/{id} with the lead form → answer-inbound answers each form | SDR · your agent, for the form | you, within 5 min |
Name the company name-the-company | Priority 1, with a public source to cite | Agent’s call, with its reason | POST /{namespace}/content/draft → POST /{namespace}/ads, paused | you approve each one, due 1 business day · Take this one | you |
Sponsor your post sponsor-your-post | your post in your top quarter at 72 h | Your rule (line 8) | POST /{namespace}/ads on the post exactly as you posted it | you grant each one · Take this one | you |
To the AE to-the-ae | a qualified Lead or a written request | Your rule (the hand-raise) | POST /{namespace}/leads/{lead}/assign → POST /{namespace}/tasks/{task}/assign, Brief attached | POST /{namespace}/tasks/{task}/assign → AE · Priya Shah, due 60 min (response-60m) · Take this one | you, within 60 min |
Take them off the List off-the-list | a Customer, a stop request, a lost Deal, or claimed by another of your Startups | Your rule (the List rule) | PATCH /{namespace}/audiences/{id} removes it → POST /{namespace}/suppressions at Company scope on a stop request | nobody; the next 06:00 upload drops it | the same |
| When they write back | Fixture reply | What happens | Who answers | By when | Call |
|---|---|---|---|---|---|
| Interested | a lead form: “Can we see it for 200 units?” | qualifies them and hands to the AE with the Brief (When it hands to you) | AE · Priya Shah (person) | 60 min | POST /{namespace}/leads/{lead}/assign |
| Send me pricing | a lead form: “What does it cost?” | instant-response answers with the Pricing Page’s list Price and one next step | SDR · your agent | 5 min | POST /{namespace}/conversations/{conversation}/answer |
| Not now | a lead form: “Next year, maybe.” | the company stays on proof ads; the Lead moves to Nurture | SDR · your agent | at once | POST /{namespace}/leads/{lead}/nurture |
| Wrong person | “I’m in accounting; our ops lead buys software.” | asks them to bring the ops lead in; it never writes to a name from their text | SDR · your agent | 1 business day | POST /{namespace}/conversations/{conversation}/answer |
| Out of office | an auto-reply to the form answer, until Nov 2 | the follow-up waits for the return date; the ads keep running | nobody | the return date | POST /{namespace}/messages/{message}/schedule |
| Question it can’t answer | an ad comment: “Does it sync with our accounting system?” | To the AE, the question in the Brief; the comment stays up | AE · Priya Shah (person) | 60 min | POST /{namespace}/conversations/{conversation}/escalate |
| Stop | “Please stop showing our team these ads.” | the company leaves every company list at the next upload, at Company scope | nobody | at once | POST /{namespace}/suppressions |
When it hands to you
Qualified Leads go to AE · Priya Shah (person); company-name ads and sponsored posts come to you, and before day 9 every qualified Lead did. What lands in each queue, as captured: one Brief per Role, then the Approval (brief, draft, route and checks are Not live yet, gaps #7).
[
{
"$id": "https://api.sb/leaseline.app/tasks/task_207" ,
"$type": "Task",
"assignee": "worker_3",
"due": "2026-09-12T15:02:00Z" ,
"on": "lead_61",
"brief": {
"who": "Tomas Reyes, Oakmere Lettings" ,
"said": "90 units for 2 owners; asked how owner statements work" ,
"chose": "to-the-ae",
"why": "a qualified Lead at a listed company",
"saw": "problem, proof and offer ads since 2026-08-28" ,
"never": [
"mention the ads",
"a price beyond the list",
"a discount"
]
}
},
{
"$id": "https://api.sb/leaseline.app/tasks/task_233" ,
"$type": "Task",
"assignee": "worker_1",
"due": "2026-10-20T17:00:00Z" ,
"on": "organization_102",
"brief": {
"who": "Calloway Residential, Priority 1" ,
"chose": "name-the-company",
"why": "Priority 1 on the List; their public site says they took on 3 new owners this year" ,
"source": "calloway-residential.example/news"
}
},
{
"$id": "https://api.sb/leaseline.app/approvals/approval_58" ,
"$type": "Approval",
"verdict": null,
"deadline": "2026-10-20T17:00:00Z" ,
"draft": "Calloway Residential: three new owners this year. Every renewal notice, 60 days out, per owner." ,
"route": "name-the-company",
"checks": [
{
"check": "approved-pages",
"status": "pass"
},
{
"check": "organization-stop",
"status": "pass"
}
]
}
]Each receiver answers with POST /{namespace}/tasks/{task}/claim (Take this one), POST /{namespace}/tasks/{task}/return with a reason, POST /{namespace}/actions/{action}/redirect, POST /{namespace}/approvals/{approval}/approve or POST /{namespace}/approvals/{approval}/refuse, each a one-call window, and an Approval unanswered by its deadline expires and the ad stays paused. Once a person claims a Lead, the agent changes nothing for that company but the List, 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 (AE to you), and person-promise fires.
On time: AE · Priya Shah, 9 of 9 claimed within 60 min, median 24 min, ■■□□ Early sign, n 9; you, 5 of 5 within 60 min and 5 of 6 approvals by their deadline (1 expired; the ad stayed paused), n 11. Your queue: counted by kind: in week 1, 1 List approval, 1 claims source and 1 change set; since, about 1 change set, 1 hand-off and 1 sponsored post 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) | Refuses | Fix | Then |
|---|---|---|---|
owned-by-a-person (rule j; Not live yet, gaps #8) | an ad change for a company whose Lead or open Deal a person owns | Leave it to the person who owns it: the draft waits in their queue. | POST /{namespace}/tasks/{task}/assign |
customer-collision (Not live yet, gaps #1) | a company list row for an active Customer | Send it to their CSM instead: the company leaves the List. | PATCH /{namespace}/audiences/{id} |
audience-guard (on every upload) | an upload with one suppressed or opted-out member, or any check that errors | Remove the rows the Guard lists, then upload again; nothing serves until then. | PATCH /{namespace}/audiences/{id} |
organization-stop (Company scope) | any ad to a company that asked to stop | Leave them off; the stop holds on every company list. | GET /{namespace}/suppressions |
approved-pages (Not live yet) | an ad sentence no approved Page or the company’s own public source backs | Cut the sentence, or publish the Page that says it. | POST /{namespace}/sites/{site}/publish |
| Stop (pauses the Play) | Pauses when | Then |
|---|---|---|
spend-limit | month-to-date spend reaches the $1,000 Limit | waits for the month; POST /{namespace}/limits/{limit}/raise with a winning or held read |
qualified-lead-floor | 60 days and $2,000 spent with no qualified Lead from a treated company | PATCH /{namespace}/campaigns/{id} to end it, or review the List rule |
cost-ceiling | a 30-day cost per qualified Lead over your $250 ceiling, with 5 or more | review the ad sets; resume with PATCH /{namespace}/campaigns/{id} |
list-floor | a company list under 300 matched members | every ad set on it pauses until it refills |
conversion-feed | no conversion for 72 h while spend runs; at 7 days the Venue pauses | POST /{namespace}/integrations/{integration}/reconnect |
feed-mismatch | the Venue’s qualified Leads differ from yours by over 30 % for 7 days | budget changes freeze; POST /{namespace}/integrations/{integration}/sync |
frequency | an ad set at double its weekly frequency target | that set pauses until its window refills |
drift | a live ad or setting differs from its approved source | resets at once; a second drift in 7 days pauses the set |
rejection | 2 Venue rejections in 7 days | new uploads pause on that Venue |
complaint | 3 or more comments call one ad creepy, a scam or surveillance | that ad waits for your review |
holdout-integrity | held-back companies reached by more than 5 % of the holdout | the Readout is void and every raise freezes |
kill-switch | you press it | every ad set pauses through the proxy and write access is revoked; POST /{namespace}/processes/{process}/enable after review |
+ 1 more for the person rung on Facebook, Instagram and LinkedIn, off in your file (?lanes=) |
The captured refusal, then the Stop that fired as a Campaign.paused reason (Not live yet, gaps #2); the upload that went after the fix is day 33 in Example.
| Check | Fix | Then |
|---|---|---|
audience-guard fail | Remove the 3 rows the Guard lists, then upload again. | PATCH /{namespace}/audiences/{id} |
customer-collision pass · Not live yet, gaps #1 | — | POST /{namespace}/leads/{lead}/assign |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "The 06:00 upload to the proof company list did not go: 3 rows match a Suppression.",
"checks": [
{
"check": "audience-guard",
"status": "fail",
"observed": "3 suppressed members of 412 rows",
"doc": "/plays/account-ads#check-audience-guard",
"fix": "Remove the 3 rows the Guard lists, then upload again.",
"then": "Audience.update"
},
{
"check": "customer-collision",
"status": "pass"
}
]
}It says no, says why, and names the fix.
[
{
"reason": {
"limit": "frequency",
"observed": "7.8 a week",
"bound": "7.4 (double the 3.7 target)",
"window": "7d"
},
"doc": "/plays/account-ads#stop-frequency" ,
"adSet": "proof"
}
]Did it work
Both halves are read from the same salted-hash assignment (rules m–o), and each figure’s call is POST /{namespace}/metrics/query by node and by arm; a company counts once its 30-day window has closed. The parent field and every node above the leaf are Not live yet (gaps #4).
+0.32 Deals per 100 listed · ■□□□ Too early to tell · n 743 matured · path → arr
- Chose
- proof-ads
- Why
- Cedar Row Management clicked the problem ads 4 times in 9 days and opened no lead form
- From
- problem-ads · proof-ads · offer-ads · name-the-company · sponsor-your-post · to-the-ae · off-the-list
- Expects
- a qualified Lead at Cedar Row within 30d
- Reopens when
- no Lead.qualified at organization_88 within 30d
- By
- worker_6
JSON
{
"$id": "decisions/decision_418",
"$type": "Decision",
"choice": "proof-ads",
"reason": "Cedar Row Management clicked the problem ads 4 times in 9 days and opened no lead form",
"stakes": "low",
"options": [
"problem-ads",
"proof-ads",
"offer-ads",
"name-the-company",
"sponsor-your-post",
"to-the-ae",
"off-the-list"
],
"by": "worker_6",
"on": "organization_88",
"expected": "a qualified Lead at Cedar Row within 30d",
"reopensWhen": "no Lead.qualified at organization_88 within 30d"
}| Node | Agent half | Your half | Added | Confidence | Basis |
|---|---|---|---|---|---|
account-deal-rate | 0.77 per 100 treated (4 of 520) | 0.45 held back (1 of 223) | +0.32 [−0.62, +1.26] | ■□□□ Too early to tell | held back at random · n 743 matured · 330 still maturing · Other Startups’ results: +0.4, ■■□□ Early sign, n 6,410 |
opened-deals-and-trials | 0.96 per 100 (5 of 520) | 0.45 (1 of 223) | +0.51 [−0.50, +1.52] | ■□□□ Too early to tell | the same assignment |
new-arr ↑ | $3,338, 2 Customers · Booked: credited, not all caused by this Play | $0, 0 Customers | +$642 per 100 listed [−$410, +$1,690] · Worth about +$1,800 a month | ■□□□ Too early to tell (2 vs 0 Customers) | the same · 3 Deals still deciding |
net-new-arr · arr | +$3,338 | $0 | +$642 per 100 listed, carried: it moves no other bridge term | ■□□□ Too early to tell | carried from new-arr |
The leaf counts Deals opened within 30 days of listing, after a held Meeting or a written request, per 100 companies assigned, to 300 matured in each half; at 520 and 223 matured the Experiment runs on, and the day-90 read lands 2026-11-22.
How it moves it: the median treated company saw 26 impressions across problem, proof and offer ads in the 9 days before its first cold email, where a held-back company saw none (the Venue’s company report).
Guardrails: treated against held back: cold-email reply rate at listed companies, 4.1 % against 3.6 %, and Meetings held per Deal opened, 1.0 against 1.0.
{
"$id": "https://api.sb/leaseline.app/campaigns/account-ads" ,
"$type": "Campaign",
"play": "play:account-ads@1" ,
"path": [
"account-deal-rate",
"opened-deals-and-trials",
"new-arr",
"net-new-arr",
"arr"
],
"experiment": {
"metric": "account-deal-rate",
"unit": "per-100",
"heldBack": 322,
"decision": null
},
"readout": {
"value": 0.32,
"n": 743,
"maturing": 330,
"interval": [
-0.62,
1.26
],
"readability": "inadmissible",
"basis": "held-back",
"pooled": false
},
"ledger": {
"spend": 1862,
"workerCost": 14,
"hours": 7.4,
"wonArr": 3338,
"projectedArr": 1850,
"paybackDays": 31
}
}Outside benchmark: 0.20 to 0.58 Deals opened per 100 companies on LinkedIn company lists, median, 211 advertisers (an ad-analytics vendor’s 2026 report).
Before it · After it. Before it: cold-email and account-visits nominate the companies, and founder-voice writes the posts it sponsors. After it: instant-response answers the lead forms, and buying-committee takes a company once 2 or more of its people engage. If the List stays under 350, run cold-email alone.
Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days). 1,083 property managers cleared the List rule; Dana struck 6 and pinned 4, which see ads and stay out of the readout. The hash held 322 of the other 1,073 back and treated 751; LinkedIn matched 709 of the 755 rows uploaded. Read every captured run at ?threads=; three follow the tables.
| What happened, treated companies, by option | Companies | Qualified Leads | Won (credited, not all caused by this Play) |
|---|---|---|---|
| Start on problem ads | 751 | 3 | 0 |
| Move to proof ads · Show the offer | 118 · 37 | 4 · 7 | 0 · 2: the $2,400 plan (+$2,393 ARR) and Team at $79 a month (+$945 ARR), 3 open |
| Name the company · Sponsor your post | 4 (3 approved) · 2 posts | 0 | 0 |
| To the AE · Take them off the List | 14 · 31 | 14 handed over | — |
| Treated · held back | 751 · 322 | 14 (1.86 per 100) · 2 (0.62) | $3,338 · $0 |
At 350 listed (holdout:) | about 245 treated, 105 held back | held back at random; too few Deals to read alone | Other Startups’ results: +0.4 Deals per 100 listed, ■■□□ Early sign, n 6,410 |
| Day | Event | What happened | Your time |
|---|---|---|---|
| 0 | event_1102 Campaign.created | the List rule, 6 strikes and 4 pins; the dry run fails on conversion-send and approved-pages | 40 min |
| 2 | event_1117 Integration.connected | LinkedIn ads connected; the test qualified Lead landed in 3 h | 10 min |
| 4 | event_1131 Audience.updated | the hash drew 322 held back; 709 of 755 rows matched after 48 h | — |
| 6 | event_1140 Approval.created, first of 3 change sets | you approved 26 of 27 concepts unedited; problem and offer ads served from day 7 | 35 min |
| 9 | event_1164 Role.staffed | AE · Priya Shah (person) takes role: | — |
| 19 | event_1243 Lead.qualified | the first qualified Lead from a treated company, Oakmere Lettings | — |
| 27 | event_1288 Campaign.paused | frequency on the proof set; it resumed when the window refilled, 2 days later | 2 min |
| 33 | event_1302 Action.refused | audience-guard held the 06:00 upload for 3 suppressed rows; the fixed upload went at 06:40 | — |
| 42 | event_1347 Change.merged | the 14-day refresh: replayed at 2.1 qualified Leads per 100, read back at +14d at 1.9 | 5 min |
| 1–56 | event_1150 Task.created, first of 23 | change sets, hand-offs and sponsored posts from the POST /{namespace}/tasks/next log | 4.8 h · total 7.4 h measured, 8 h asked |
Wrenfield Rentals, listed 2026-08-28 · 212 units; the cold-email cell of Sep 6 joined the company list 9 days early. Problem ads from day 7: “Renewals slip when notices go out late.”¹
Sep 9 · 4 clicks · moved to proof ads: “Leaseline sends every renewal notice 60 days out, per owner.”² Sep 14 · a pricing Page read moved it to the offer set and its lead form.³
Sep 15 · Elena Park filled the lead form ·
POST /{namespace}/leads/{lead}/qualify, thenPOST /{namespace}/leads/{lead}/assignto AE · Priya Shah, who held the call on Sep 19; Wrenfield took the $2,400 plan on Oct 4 (+$2,393 ARR).
- Leaseline cuts renewal work in half for every property manager.
+ Leaseline sends every renewal notice 60 days out, per owner, from your own lease dates. Oakmere Lettings, 2026-09-12 14:02 · Tomas Reyes became a qualified Lead after the offer ads.
POST /{namespace}/leads/{lead}/assign→ AE · Priya Shah, due 15:02 (response-60m); she claimed it at 14:26 with the Brief (task_207), which tells her never to mention the ads.⁴ Oakmere took Team on Sep 23 (+$945 ARR).
¹ Problem ads name the problem, never the company, a Visit or a click. ² Every sentence maps to an approved Page. ³ The page read chose the ad set; it never appears in an ad or a Message. ⁴ No Message from a sending Play mentions these ads.
Your settings
---
holdout: 0.3 # 30 % of the List sees no ads from this Play
budget: 1000 # a month, held by a Limit
---
List property managers with 50 to 500 units.
Start ads 9 days before the first cold email.
Refresh each concept every 14 days.
Sponsor my posts that rank in my top quarter.$ npx startup-builder show plays/account-ads
holdout 0.3 · budget 1000 · List rule · 9-day lead · 14-day refresh · sponsored posts your file plays/account-ads.mdx:2,3,5,6,7,8 (Ask 2026-08-24; line 7 by change_22, 2026-10-05)
family demand · worker role:marketing-lead, owns never · chooses 7 (4 rule, 3 read) · approval every-send · person rung off · checks owned-by-a-person, customer-collision, audience-guard, organization-stop, approved-pages · stops 12 + person-rung catalog play:account-ads@1 (Not live yet, gaps #5) Every key from the catalog
Compiled, not written: edit the Play record account-ads 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.