URL: /plays/account-ads · Id: `play:account-ads`

# 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.

Shows ads to fitting companies on your list a week before the first email, then counts the [Deals](https://startupbuilder.dev/business/nouns/deal) they open against companies held back.

```text title="Set it up · Ask"
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.
```

```text title="Run one · Ask"
Hand our latest engaged company to the Marketing Lead agent and show me what it chose and why. Don’t spend.
```

```ts title="Run one · SDK"
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 }
```

```json title="Run one · Response · sample"
{
  "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",
      "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"
  },
  "actions": [
    {
      "$id": "https://api.sb/leaseline.app/actions/action_1",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_6",
      "verb": "Organization.get",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_2",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_6",
      "verb": "Audience.update",
      "checks": [
        {
          "check": "owned-by-a-person",
          "status": "pass"
        },
        {
          "check": "customer-collision",
          "status": "pass"
        },
        {
          "check": "audience-guard",
          "status": "pass"
        },
        {
          "check": "organization-stop",
          "status": "pass"
        }
      ]
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_3",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingApproval",
      "mode": "try",
      "by": "worker_6",
      "verb": "Ad.update",
      "checks": [
        {
          "check": "approved-pages",
          "status": "pass"
        }
      ]
    }
  ]
}
```

`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 |

| Worth | Your time | Spend | First signal | Use it when | Instead |
| --- | --- | --- | --- | --- | --- |
| ≈ +$1,800 ARR a month at 750 listed | 60 min week 1, then 1 h a week | $1.33 per listed company, $1,000 a month | Day 19: first qualified Lead from a listed company | 350+ fitting companies, a sending Play, $1,000 a month | Under 350: `cold-email` · under $1,000 a month: `founder-voice` |

**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/leaseline.app · 41 Customers at 2026-10-19 · on Ad.clicked · first ad live 9 days before the first email · 4 keys Not live yet`

## Start it this week
```bash
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"}'
```


```json title="The dry run’s 422 · 2026-08-24 · sample"
{
  "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"
    }
  ]
}
```

| Check | Fix (one call) | Your time | Wait |
| --- | --- | --- | --- |
| <a id="check-owned-by-a-person"></a>`owned-by-a-person` · <a id="check-use-when"></a>`use-when` · <a id="check-baseline"></a>`baseline` · pass: 0 person-owned Deals on the List; 1,083 fitting companies; 0.31 Deals per 100, n 1,083 (gaps #8) | <Op verb="Metric.query" /> on `account-deal-rate`, which fills Before in Your number | 1 min | none |
| <a id="check-conversion-send"></a>`conversion-send` · fail (Not live yet) | <Op verb="Integration.connect" /> with `venue` linkedin, then one test qualified Lead from your records | 10 min | the test conversion, under 24 h |
| <a id="check-approved-pages"></a>`approved-pages` · fail: no Customer result Page (Not live yet) | <Op verb="Site.publish" /> the Page; until then problem and offer ads run, proof stays off | 20 min | none |
| <a id="check-role-unstaffed"></a>`role-unstaffed` · warn (gaps #8): no AE, so qualified Leads come to you | <Op verb="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 (<Op verb="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 | <Op verb="Audience.update" /> 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 <Op verb="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 | <Op verb="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=all`) | | | | |

```json title="One signed delivery · StartupBuilder-Signature"
{
  "$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"
  }
}
```

```bash title="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":"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:marketing-lead`, `worker_6`) holds the Key Result and runs the ad sets as Paid media (`role:paid-media-manager`). It hands qualified Leads to **AE · Priya Shah (person)** (`role:sales-lead`, `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 | <Op verb="Approval.approve" /> · <Op verb="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 | <Op verb="Action.redirect" /> |
| Name the company · Sponsor your post | You approve each one, on every rung | 3 of 4 · 2 of 2 | never | — | <Op verb="Approval.approve" /> · <Op verb="Approval.refuse" /> |
| Change the List rule or the refresh | You approve each one | 1 merged of 1 | never | — | <Op verb="Change.merge" /> · <Op verb="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 | — | <Op verb="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 <Op verb="Audience.update" />, <Op verb="Ad.create" />, <Op verb="Ad.update" />, <Op verb="Content.draft" />, <Op verb="Lead.assign" />, <Op verb="Task.assign" />, <Op verb="Suppression.create" />, and any other call becomes an Approval under the Role’s Mandate.

```md title="The brief (folded): compiled Agent-register Markdown · play:account-ads@1 · each line names its record key"
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 <Op verb="Change.merge" />. **Replayed on past weeks**, <Op verb="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. <Op verb="Role.staff" /> seats it.

```bash
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) | <Op verb="Audience.update" /> adds it to the problem list → <Op verb="Ad.create" /> 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 | <Op verb="Audience.update" /> moves it → <Op verb="Ad.update" /> 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 | <Op verb="Audience.update" /> → <Op verb="Ad.update" /> 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 | <Op verb="Content.draft" /> → <Op verb="Ad.create" />, 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) | <Op verb="Ad.create" /> 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) | <Op verb="Lead.assign" /> → <Op verb="Task.assign" />, Brief attached | <Op verb="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) | <Op verb="Audience.update" /> removes it → <Op verb="Suppression.create" /> 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 | <Op verb="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 | <Op verb="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 | <Op verb="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 | <Op verb="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 | <Op verb="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 | <Op verb="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 | <Op verb="Suppression.create" /> |

## 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).

```json title="What lands in each queue · sample"
[
  {
    "$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 <Op verb="Task.claim" /> (Take this one), <Op verb="Task.return" /> with a reason, <Op verb="Action.redirect" />, <Op verb="Approval.approve" /> or <Op verb="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 <Op verb="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:** <Op verb="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. | <Op verb="Task.assign" /> |
| <a id="check-customer-collision"></a>`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. | <Op verb="Audience.update" /> |
| <a id="check-audience-guard"></a>`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. | <Op verb="Audience.update" /> |
| <a id="check-organization-stop"></a>`organization-stop` (Company scope) | any ad to a company that asked to stop | Leave them off; the stop holds on every company list. | <Op verb="Suppression.list" /> |
| `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. | <Op verb="Site.publish" /> |

| Stop (pauses the Play) | Pauses when | Then |
| --- | --- | --- |
| <a id="stop-spend-limit"></a>`spend-limit` | month-to-date spend reaches the $1,000 Limit | waits for the month; <Op verb="Limit.raise" /> with a winning or held read |
| <a id="stop-qualified-lead-floor"></a>`qualified-lead-floor` | 60 days and $2,000 spent with no qualified Lead from a treated company | <Op verb="Campaign.update" /> to end it, or review the List rule |
| <a id="stop-cost-ceiling"></a>`cost-ceiling` | a 30-day cost per qualified Lead over your $250 ceiling, with 5 or more | review the ad sets; resume with <Op verb="Campaign.update" /> |
| <a id="stop-list-floor"></a>`list-floor` | a company list under 300 matched members | every ad set on it pauses until it refills |
| <a id="stop-conversion-feed"></a>`conversion-feed` | no conversion for 72 h while spend runs; at 7 days the Venue pauses | <Op verb="Integration.reconnect" /> |
| <a id="stop-feed-mismatch"></a>`feed-mismatch` | the Venue’s qualified Leads differ from yours by over 30 % for 7 days | budget changes freeze; <Op verb="Integration.sync" /> |
| <a id="stop-frequency"></a>`frequency` | an ad set at double its weekly frequency target | that set pauses until its window refills |
| <a id="stop-drift"></a>`drift` | a live ad or setting differs from its approved source | resets at once; a second drift in 7 days pauses the set |
| <a id="stop-rejection"></a>`rejection` | 2 Venue rejections in 7 days | new uploads pause on that Venue |
| <a id="stop-complaint"></a>`complaint` | 3 or more comments call one ad creepy, a scam or surveillance | that ad waits for your review |
| <a id="stop-holdout-integrity"></a>`holdout-integrity` | held-back companies reached by more than 5 % of the holdout | the Readout is void and every raise freezes |
| <a id="stop-kill-switch"></a>`kill-switch` | you press it | every ad set pauses through the proxy and write access is revoked; <Op verb="Process.enable" /> after review |
| + 1 more for the person rung on Facebook, Instagram and LinkedIn, off in your file (`?lanes=all`) | | |

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.

```json title="The captured refusal · sample"
{
  "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"
    }
  ]
}
```


```json title="The Stops that fired, as Campaign.paused reasons · sample"
[
  {
    "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 <Op verb="Metric.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`

| 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.

```json title="The Campaign card · sample"
{
  "$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=all`; 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: 0.3`) | 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:sales-lead` | — |
| 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 <Op verb="Task.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** · <Op verb="Lead.qualify" />, then <Op verb="Lead.assign" /> to AE · Priya Shah, who held the call on Sep 19; Wrenfield took the $2,400 plan on Oct 4 (+$2,393 ARR).

```diff title="Proof ad, 2026-09-14 · the concept approved-pages refused, then the rewrite that served"
- 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. <Op verb="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
```mdx title="plays/account-ads.mdx"
---
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.
```

```text
$ 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
| Key | Id | What it is |
| --- | --- | --- |
| Tactic | <a id="tactic-named-list-build"></a>`named-list-build` | Named list build names fitting Organizations from the ICP, resolved Visits and sending Plays’ cells, and ranks them by fit times value. Starts at You approve the first wave. |
| Tactic | <a id="tactic-named-list-holdout"></a>`named-list-holdout` | Named list holdout puts 30 percent of the list in a holdout by a salted hash, and excludes it from every LinkedIn Campaign. Starts at Sends nothing. |
| Tactic | <a id="tactic-company-list-upload"></a>`company-list-upload` | Company list upload keeps the treated Organizations on LinkedIn as a company list of 300 rows and 300 matched members or more, current daily. Starts at You approve each one. |
| Tactic | <a id="tactic-creative-ladder"></a>`creative-ladder` | Creative ladder runs problem, proof and offer ad sets on one company list, moved by engagement and paced by membership windows. Starts at You approve each one. |
| Tactic | <a id="tactic-ladder-creative"></a>`ladder-creative` | Ladder creative drafts three distinct concepts per ad set from the claims source, retires the tired ones, and refreshes every 21 days. Starts at You approve each one. |
| Tactic | <a id="tactic-founder-post-sponsoring"></a>`founder-post-sponsoring` | Founder post sponsoring proposes the founder’s own strongest LinkedIn posts for sponsoring to the company list, and sponsors each only on the founder’s grant. Starts at You approve each one. |
| Tactic | <a id="tactic-qualified-lead-bidding"></a>`qualified-lead-bidding` | Qualified lead bidding sends qualified Leads and opened Deals to each Venue within 24 hours, and bids on them, never on form fills. Starts at Sends nothing. |
| Tactic | <a id="tactic-air-cover-clock"></a>`air-cover-clock` | Air cover clock lists a sending Play’s Organizations 9 days before its first email, and releases the email after 7 days of Ads. Starts at Sends nothing. |
| Tactic | <a id="tactic-named-audience-rung"></a>`named-audience-rung` | Named audience rung adds US people at treated Organizations to LinkedIn and Facebook Audiences, from first-party and person-rung sources only. Starts at You approve the first wave. |
| Tactic | <a id="tactic-audience-guard-check"></a>`audience-guard-check` | Audience guard check runs before every Audience upload and refuses the whole upload on any failed or errored check. Starts at Sends nothing. |
| Tactic | <a id="tactic-account-deal-readout"></a>`account-deal-readout` | Account deal readout counts qualified Leads and Deals per 100 Organizations by arm, from the CRM, and gates every raise on the result. Starts at You approve each one. |
| Tactic | <a id="tactic-ad-write-proxy"></a>`ad-write-proxy` | Ad write proxy is the only write path to an ads Venue, holding the token outside the model and forcing every create to paused. Starts at You approve each one. |
| Tactic | <a id="tactic-ad-drift-monitor"></a>`ad-drift-monitor` | Ad drift monitor diffs every live Ad’s rendered creative, text and enhancement settings against the approved source daily, and pauses or resets what drifted. Starts at Sends nothing. |
| Tactic | <a id="tactic-ad-tag-consent-gate"></a>`ad-tag-consent-gate` | Ad tag consent gate decides on every Visit which Venue tags load, by the Visitor’s Market, Consent and opt-out signal, before any tag fires. Starts at Sends nothing. |
| Trigger | <a id="trigger-organization-listed"></a>`organization-listed` | Organization listed: `state` on `Organization.listed`, window 14d. |
| Trigger | <a id="trigger-air-cover-requested"></a>`air-cover-requested` | Air cover requested: `event` on `Message.scheduled`, window 14d. |
| Trigger | <a id="trigger-pricing-visit"></a>`pricing-visit` | Pricing visit: `event` on `Visit.recorded`, window 72h. |
| Trigger | <a id="trigger-organization-surge"></a>`organization-surge` | Organization surge: `threshold` on `Visitor.identified`, window 7d. |
| Trigger | <a id="trigger-organization-ad-engagement"></a>`organization-ad-engagement` | Organization ad engagement: `threshold` on `Ad.clicked`, window 14d. |
| Trigger | <a id="trigger-qualified-lead-recorded"></a>`qualified-lead-recorded` | Qualified lead recorded: `event` on `Lead.qualified`, window 1d. |
| Trigger | <a id="trigger-listed-deal-opened"></a>`listed-deal-opened` | Listed deal opened: `event` on `Deal.created`, window 1d. |
| Trigger | <a id="trigger-listed-organization-exit"></a>`listed-organization-exit` | Listed organization exit: `state` on `Organization.delisted`, window 1d. |
| Trigger | <a id="trigger-founder-post-candidate"></a>`founder-post-candidate` | Founder post candidate: `threshold` on `Post.published`, window 7d. |
| Trigger | <a id="trigger-audience-floor-crossed"></a>`audience-floor-crossed` | Audience floor crossed: `threshold` on `Audience.resized`, window 7d. |
| Trigger | <a id="trigger-opt-out-received"></a>`opt-out-received` | Opt-out received: `event` on `Consent.withdrawn`, window 1d. |
| Trigger | <a id="trigger-creative-drift"></a>`creative-drift` | Creative drift: `event` on `Monitor.checked`, window 1d. |
| Trigger | <a id="trigger-holdout-closed"></a>`holdout-closed` | Holdout closed: `event` on `Experiment.matured`, window 7d. |
| Benchmark | <a id="benchmark-account-ads-deal-open"></a>`account-ads-deal-open` | Deals opened per company targeted with LinkedIn company-list Ads, median: 0.2–0.58 % (An ad-analytics vendor’s 2026 LinkedIn company-list benchmark report, 2026-10-24). |
| Benchmark | <a id="benchmark-account-ads-ctr"></a>`account-ads-ctr` | Click-through rate on LinkedIn company-list Ads, median: 0.69 % (An ad-analytics vendor’s 2026 LinkedIn company-list benchmark report, 2026-10-24). |
| Benchmark | <a id="benchmark-account-ads-impressions-per-company"></a>`account-ads-impressions-per-company` | Impressions per targeted company on LinkedIn company-list Ads, median: 27.3 count (An ad-analytics vendor’s 2026 LinkedIn company-list benchmark report, 2026-10-24). |