URL: /plays/paid-social · Id: `play:paid-social`

# Paid social

> Runs 10 to 15 distinct ad concepts at a time, each paused until approved, and spends more only after a holdout shows added sales.

Runs 10 to 15 distinct ad concepts at a time, each paused until approved, and spends more only after a holdout shows added sales.

```text title="Set it up · Ask"
Be our Marketing Lead for social ads: $1,000 a month on LinkedIn and Facebook, 12 distinct concepts a batch, each paused until I approve it. Judge them on trial starts, never clicks, and spend more only after a holdout shows they add sales. Show me what’s missing; don’t spend.
```

```text title="Run one · Ask"
Hand the latest ad set that is ready to judge to the Marketing Lead agent and show me what it chose and why. Don’t change anything.
```

```ts title="Run one · SDK"
import { $ } from 'startup-builder'

const { events: [last] } = await $.Event.list({ where: 'name in ("Experiment.started", "Metric.watched", "Experiment.matured", "Monitor.checked", "Ad.rejected", "Experiment.concluded", "Conversation.closed", "Audience.refreshed", "Invoice.collected", "Organization.announced", "Offer.repriced")', 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_589",
    "$type": "Decision",
    "choice": "judge-the-concepts",
    "reason": "batch 4 reached 54 trial starts in 7 days; concepts 3 and 8 beat the batch’s cost per trial by 40 % or more, and 7 concepts spent 3 times the target with none",
    "stakes": "low",
    "options": [
      "refresh-the-concepts",
      "judge-the-concepts",
      "reset-the-drift",
      "carry-an-angle",
      "take-the-next-step",
      "not-now"
    ],
    "by": "worker_6",
    "on": "experiment_9",
    "expected": "cost per trial start down 20 % over the next 7 days",
    "reopensWhen": "cost per trial start not down 20 % at the next Experiment.matured on experiment_9"
  },
  "actions": [
    {
      "$id": "https://api.sb/leaseline.app/actions/action_1",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_6",
      "verb": "Experiment.conclude",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_2",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingTime",
      "mode": "try",
      "by": "worker_6",
      "verb": "Ad.update",
      "checks": [
        {
          "check": "scale-gate",
          "status": "pass"
        }
      ]
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_3",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingApproval",
      "mode": "try",
      "by": "worker_6",
      "verb": "Content.draft",
      "checks": [
        {
          "check": "fact-check",
          "status": "pass"
        },
        {
          "check": "synthetic-media",
          "status": "pass"
        }
      ]
    }
  ]
}
```

`action_2` pauses the 7 losers through the proxy within 15 minutes, which you can stop. `action_3` drafts 3 new concepts for the bottom quarter, paused until you approve them; no budget moves.

`+16.7 per $100 · ■■□□ Early sign · n 8 weeks · path → arr`

| Try another: the ad set | Choice | Reason |
| --- | --- | --- |
| Batch 4 on LinkedIn: 54 trial starts in 7 days, 12 concepts | `judge-the-concepts` | enough events to judge: 2 winners, 7 losers |
| Batch 3 reached day 21 | `refresh-the-concepts` | the refresh date: retire the undecided, draft the bottom quarter |
| The live Facebook ad “60 days out” shows a caption no one approved | `reset-the-drift` | a Venue enhancement added it: reset at once |
| The renewal-timing Post won on LinkedIn organic: 4 times the median Contacts | `carry-an-angle` | an angle that won elsewhere: 2 paid concepts for your OK |
| The 8-week switchback: +16.7 per $100; breakeven at 12 | `take-the-next-step` | above breakeven, and the angle won twice: a raise to $1,500, with your OK |

| Worth | Your time | Spend | First signal | Use it when | Instead |
| --- | --- | --- | --- | --- | --- |
| ≈ +$1,150 ARR a month at $1,000 spend | 2 h week 1, then 40 min a week | $600 a month at least; $1,000 here | Day 5: first trial from a paid Visit | $600+ a month on one Venue, a known margin | Under $600 a month: `organic-social` · no angle has won: `positioning-tournament` |

**Where it sits:** **Stranger** → Lead → Trialing → Activated → At risk → Churned → Won back · Social ICP Visits per $100 → People reached → New Customers ↑ → ARR added each week → ARR

Worth’s formula: about +16.7 incremental ICP Visits per $100 (your switchback, ■■□□ Early sign) × $1,000 a month = 167 Visits × 0.02 Visits to paid × $347 Pro ARR ≈ +$1,159. Under First signal: An 8-week switchback of alternating weeks tells a +10 lift per $100 at $1,000 a month; each scale step waits for a 28-day holdout read.

`sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on Experiment.matured · verdict 11 min after the ad set matured · 9 keys Not live yet`

## Start it this week
```bash
npx startup-builder add play paid-social   # 1 · writes plays/paid-social.mdx (Not live yet, gaps #5)

# 2 · dry run; below, its 422 on 2026-08-24
curl -X POST "https://api.sb/leaseline.app/campaigns?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"paid-social"}'

# 3 · keep it: every concept waits for your OK
curl -X POST https://api.sb/leaseline.app/campaigns \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"paid-social"}'

# the monthly Limit
curl -X POST https://api.sb/leaseline.app/limits \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"paid-social-month","amount":1000,"period":"month"}'

# off: every Campaign pauses through the proxy, and write scope is revoked
curl -X PATCH https://api.sb/leaseline.app/campaigns/paid-social \
  -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": "paid-social can’t start yet: 1 check fails and 1 warns.",
  "checks": [
    {
      "check": "use-when",
      "status": "pass",
      "observed": "$1,000 a month; Pro’s margin $29 a month, $347 a year",
      "doc": "/plays/paid-social#check-use-when",
      "fix": "Nothing to do: it needs $600 a month and a known margin.",
      "then": "Metric.query"
    },
    {
      "check": "baseline",
      "status": "warn",
      "observed": "0 weeks of paid spend: no switchback yet",
      "doc": "/plays/paid-social#check-baseline",
      "fix": "n 0: the switchback’s off-weeks become the baseline in 8 weeks.",
      "then": "Metric.query"
    },
    {
      "check": "billing-connected",
      "status": "fail",
      "doc": "/plays/paid-social#check-billing-connected",
      "fix": "Connect Stripe, so every gate reads first sales from your own records.",
      "then": "Integration.connect"
    },
    {
      "check": "fact-check",
      "status": "pass",
      "observed": "the claims source holds 9 claims",
      "doc": "/plays/paid-social#check-fact-check",
      "fix": "Nothing to do: every concept line must cite one of the 9.",
      "then": "Content.draft"
    },
    {
      "check": "role-unstaffed",
      "status": "pass",
      "observed": "every approval comes to you",
      "doc": "/plays/paid-social#check-role-unstaffed",
      "fix": "Nothing to do: a founder-only Startup approves every concept.",
      "then": "Role.staff"
    }
  ]
}
```

| Check | Fix (one call) | Your time | Wait |
| --- | --- | --- | --- |
| <a id="check-use-when"></a>`use-when` · <a id="check-baseline"></a>`baseline` · pass: $1,000 a month; warn: no switchback yet (gaps #8) | <Op verb="Metric.query" /> on `social-icp-visits-per-100`, which fills Before as the off-weeks run | 1 min | 8 weeks for the first read |
| <a id="check-billing-connected"></a>`billing-connected` · fail | <Op verb="Integration.connect" /> with `system` recurring-billing (Stripe) | 5 min | the first sync, about 10 min |
| <a id="check-fact-check"></a>`fact-check` · pass: 9 claims (Not live yet) | nothing; it runs again on every concept | 0 min | none |
| <a id="check-role-unstaffed"></a>`role-unstaffed` · pass: 0 staffing fails on a founder-only Startup | nothing; every concept and step comes to you | 0 min | none |

`Your number · Before 0 · Now 16.7 per $100 · Target 15 by Dec 31 · ■■□□ Early sign · n 8 weeks · Other Startups +8.2, ■■■□ Likely`

Media spend is $1,000 a month under its Limit; renders cost about $1.40 a concept under the render Limit, and the agent about $0.60 a day (Worker cost). That comes to about $1,035 a month at 12 concepts every 21 days.

## When it starts
| When | Trigger id | Starts on | Window | How it gets in |
| --- | --- | --- | --- | --- |
| An ad set reaches about 50 trial starts in 7 days, or runs out of time | `verdict-ready` | `threshold` on `Experiment.matured` where optimizationEvents >= 50 in 7d | judged within 1 h | <Op verb="Experiment.start" /> set the ad set; the Venue reports through <Op verb="Integration.connect" /> |
| A concept batch reaches its 21st day | `refresh-due` | `at` on `Experiment.started` where batch.startedAt + 21d | the next batch within 7d | the batch date in your file |
| What runs on the Venue differs from what you approved | `creative-drift` | `event` on `Monitor.checked` where matchesApproved = false | reset within 1d | the daily <Op verb="Monitor.check" /> of every live Ad |
| + 9 more: a tired winner, a rejected Ad, a won angle, new Customer words, a refreshed keeper list, a first sale, a rival’s moment, a margin change and a closed holdout (`?lanes=all`) | | | | |

```json title="One signed delivery · StartupBuilder-Signature"
{
  "$id": "https://api.sb/leaseline.app/events/event_1410",
  "$type": "Event",
  "name": "Experiment.matured",
  "at": "2026-10-19T07:12:44Z",
  "seq": 1410,
  "data": {
    "experiment": {
      "$id": "https://api.sb/leaseline.app/experiments/experiment_9",
      "venue": "linkedin",
      "batch": 4,
      "concepts": 12
    },
    "events": {
      "kind": "trial-start",
      "count": 54,
      "days": 7
    }
  }
}
```

```bash title="Reproduce it in test"
npx startup-builder trigger Experiment.matured --like event_1410   # replays the delivery in test; the proxy writes nothing live
npx startup-builder clock advance --by 15m   # pauses the 7 losers (action_2) unless you stop it
npx startup-builder listen Invoice.collected --forward-to http://localhost:3000/hook
```

## Who runs it
**Marketing Lead · your agent** (`role:marketing-lead`, `worker_6`) holds the Key Result and writes to the ads Venues only through the fenced proxy, which creates every Ad paused. **You** (Dana Okafor, person, `worker_1`) approve every concept, every carried angle and every spend step. Your number links to Did it work.

| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
| --- | --- | --- | --- | --- | --- |
| Judge the concepts · Reset what drifted | It does it, you can stop it | 4 verdicts · 2 resets | met on day 28 (the first 3 verdicts unedited) | a `drift` or `fence-breach` → You approve each one | <Op verb="Action.cancel" /> before the proxy writes |
| Refresh the concepts · Carry a won angle into ads | You approve each one | 41 of 48 concepts approved · 2 of 2 | never: a new concept is yours to approve | — | <Op verb="Approval.approve" /> · <Op verb="Approval.refuse" /> |
| Take the next spend step | You approve each one | 1 asked, waiting on you | never: a spend step is yours | — | <Op verb="Approval.approve" />, then <Op verb="Limit.raise" /> |
| Not now | It does it | 2 of 2 | — | — | <Op verb="Action.redirect" /> |
| Change the concept rule | You approve each one | 1 merged of 1 | never | — | <Op verb="Change.merge" /> · <Op verb="Change.revert" /> |
| Never: a synthetic Customer, a face or voice with no yes, a fake play button | Refused, every time | 1 drafted, 1 refused | never | — | none: the Check holds it |

**What it reads · What it may call.** It reads trial starts, first sales from Stripe, spend and the approved sources, never click-through rate or the Venue’s own return on ad spend. Watched behavior never appears in Ad text, and tracking is added by the proxy at publish. Its tools are <Op verb="Content.draft" />, <Op verb="Ad.create" />, <Op verb="Approval.create" />, <Op verb="Experiment.conclude" />, <Op verb="Ad.update" />, <Op verb="Limit.raise" />, <Op verb="Experiment.start" />, and any other call becomes an Approval under the Role’s Mandate.

```md title="The brief (folded): compiled Agent-register Markdown · play:paid-social@1 · each line names its record key"
You hold the Marketing Lead Key Result: 15 incremental ICP Visits per $100 by 2026-12-31. ← worker, metric
On each verdict, refresh, drift or holdout, choose one option and say why.       ← triggers, chooses
12 distinct concepts a batch, a quarter of them exploration.                     ← your file, line 5
No testimonial a real Customer didn’t give; no synthetic person speaks as a Customer. ← copyRules 3–4
Label every realistic synthetic person, voice or scene.                          ← copyRules 5
No spend step without a winning holdout read, an angle that won twice, and Dana’s OK. ← Scale gate, chooses.take-the-next-step
```

**The slow loop, as a Change.** `decision_405` (2026-09-21, weekly) read batches 1 and 2: 3 of 5 winners were product-screen concepts and 0 of 6 were stock-photo concepts. `change_30` made at least half of each batch product-screen concepts (your file, line 6), and Dana merged it with <Op verb="Change.merge" />. **Replayed on past batches**, <Op verb="Change.review" /> put winner share at 25 %; batch 4 read back at 17 % (2 of 12), n 12 concepts, ■□□□ Too early to tell (Example, day 56).

**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; it still writes only through the proxy.

```bash
npx startup-builder eval --scenario paid-social-12 --agent "claude -p" --seat marketing-lead   # seated: 17 % winner share, 0 Checks failed sent · yours: 8 %, 1 unlabeled render → You approve each one
```

## What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
| --- | --- | --- | --- | --- | --- |
| Refresh the concepts `refresh-the-concepts` | the batch’s 21st day, or a tired winner | Your rule (line 5) | <Op verb="Content.draft" /> 12 concepts → <Op verb="Ad.create" />, paused → <Op verb="Approval.create" /> | you approve the batch · Take this one | you |
| Judge the concepts `judge-the-concepts` | about 50 trial starts in 7 days, or out of time | Your rule (the Concept stop) | <Op verb="Experiment.conclude" /> → <Op verb="Ad.update" /> to pause the losers | nobody | the same |
| Reset what drifted `reset-the-drift` | a live Ad or setting differs from its approved source | Your rule (the Drift stop) | <Op verb="Ad.update" /> back to the approved state | nobody | the same |
| Carry a won angle into ads `carry-an-angle` | an angle won elsewhere, or Customers used new words | Agent’s call, with its reason | <Op verb="Content.draft" /> 2 concepts from it → <Op verb="Approval.create" /> | you approve them · Take this one | you |
| Take the next spend step `take-the-next-step` | a holdout read beats breakeven, and the angle won twice | Agent’s call, with its reason | <Op verb="Approval.create" /> → on yes, <Op verb="Limit.raise" /> → <Op verb="Experiment.start" /> for the next 28-day read | you approve it · Take this one | you |
| Not now `not-now` | no holdout read, or a read under breakeven | Agent’s call, with its reason | nothing; names what holds it: the open Experiment or `holdout-floor` | nobody | the same |

| When they write back | Fixture reply | What happens | Who answers | By when | Call |
| --- | --- | --- | --- | --- | --- |
| Interested | a lead form from the ad: “We manage 200 units. Demo?” | the form starts `instant-response`, which answers and books; this Play only counts the paid Visit | SDR · your agent, then AE · Priya Shah | 5 min | <Op verb="Lead.create" /> |
| Send me pricing | a comment: “How much is it?” | one reply from the Company Page with Pro’s live Price and its link | Marketing Lead · your agent | 1 business day | <Op verb="Comment.create" /> |
| Not now | a comment: “Not for us.” | nothing; no one is followed or retargeted from a comment | nobody | — | <Op verb="Decision.list" /> |
| Wrong person | a comment tagging a colleague | nothing writes to the person tagged | nobody | — | <Op verb="Decision.list" /> |
| Out of office | an off-week in the switchback | no Ad shows that week; it is the comparison | nobody | — | <Op verb="Experiment.get" /> |
| Question it can’t answer | a comment: “Does it work for HOAs in Canada?” | To a person: a reply only you can give, with the true wait | you (Dana Okafor, person) | 1 business day | <Op verb="Task.assign" /> |
| Stop | “Stop showing me this.”, through the Venue’s ad preferences | the Venue stops the Ad for them; 3 comments calling an Ad fake or AI-made pause it (`comment`) | nobody | at once | <Op verb="Decision.list" /> |

## When it hands to you
Every concept batch, carried angle, spend step and unanswerable comment comes to **you**: the Marketing Lead Role has no person, and spend is never the agent’s call. One Brief, as captured, then the Approval (`brief`, `draft` and `checks` are Not live yet, gaps #7).

```json title="What lands in each queue · sample"
[
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_469",
    "$type": "Task",
    "assignee": "worker_1",
    "due": "2026-10-21T17:00:00Z",
    "on": "experiment_8",
    "brief": {
      "what": "the 8-week switchback",
      "read": "+16.7 incremental ICP Visits per $100 [+3.2, +30.2], breakeven 12",
      "chose": "take-the-next-step",
      "why": "above breakeven; the renewal-timing angle won on LinkedIn organic and in batch 3",
      "step": "raise the Limit to $1,500 a month",
      "never": [
        "a raise without your OK"
      ]
    }
  },
  {
    "$id": "https://api.sb/leaseline.app/approvals/approval_99",
    "$type": "Approval",
    "verdict": null,
    "deadline": "2026-10-21T17:00:00Z",
    "draft": "3 concepts, paused: “Renewal notices, 60 days out, per owner” (product screen, 15 s)…",
    "route": "refresh-the-concepts",
    "checks": [
      {
        "check": "fact-check",
        "status": "pass"
      },
      {
        "check": "synthetic-media",
        "status": "pass"
      }
    ]
  }
]
```

You answer with <Op verb="Approval.approve" /> or <Op verb="Approval.refuse" />, or <Op verb="Task.claim" /> (Take this one) and <Op verb="Task.return" /> with a reason, each a one-call window. An unapproved concept never goes live, and an unanswered spend step never happens; live Ads keep running at the current Limit.

**On time:** you, 5 of 5 concept batches approved within 2 days, median 5 h, n 5, ■■□□ Early sign beside Other Startups’ 90 % on time; the spend step of Oct 19 waits for you. **Your queue:** counted by kind: one concept batch every 21 days, about 1 carried angle a fortnight, and a spend step at each holdout read.

**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 |
| --- | --- | --- | --- |
| `fact-check` (Not live yet) | a concept line the claims source lacks, or a Price that differs from the live Price | Cut the claim, or name the record that backs it. | <Op verb="Content.draft" /> |
| <a id="check-synthetic-media"></a>`synthetic-media` (the synthetic media check Tactic) | an invented testimonial, a synthetic person speaking as a Customer, an unlabeled realistic render, or a face or voice with no yes | Use a real Customer’s words with their yes, or show the product instead. | <Op verb="Content.draft" /> |
| <a id="check-scale-gate"></a>`scale-gate` (the record’s Scale gate) | a Limit raise or a new Venue with no winning holdout read and an angle that won twice | Run the 28-day holdout first; the angle must win twice. | <Op verb="Experiment.start" /> |
| <a id="check-holdout-floor"></a>`holdout-floor` (the record’s Holdout floor) | a raise when incremental return sits under the breakeven ratio | Spend holds at the last winning Limit; refresh the concepts instead. | <Op verb="Content.draft" /> |

| Stop (pauses the Play) | Pauses when | Then |
| --- | --- | --- |
| <a id="stop-payback"></a>`payback` | a 14-day cost per first sale above first-sale margin, at 5 or more first sales | the Play pauses |
| <a id="stop-zero-transaction"></a>`zero-transaction` | 14-day spend over 3 times the margin with no first sale | the Play pauses |
| <a id="stop-concept"></a>`concept` | an Ad spends 3 times the target cost per trial start with none | that Ad pauses as a loser |
| <a id="stop-spend-limit"></a>`spend-limit` | month-to-date spend reaches the monthly Limit | the Play pauses until the month turns |
| <a id="stop-conversion-feed"></a>`conversion-feed` | no conversion for 72 h while clicks flow | budget changes freeze; at 7 days the Play pauses |
| <a id="stop-feed-mismatch"></a>`feed-mismatch` | the Venue’s first sales differ from Stripe’s by over 30 % for 7 days | budget changes freeze |
| <a id="stop-drift"></a>`drift` | a live Ad or setting differs from its approved source | it pauses or resets at once; a second in 7 days pauses the ad set |
| <a id="stop-enhancement"></a>`enhancement` | a Venue enhancement found on without an opt-in Decision | it resets at once; a second in 7 days pauses that Venue |
| <a id="stop-disclosure"></a>`disclosure` | a live Ad with a realistic synthetic person, voice or scene and no label | it pauses at once; the Play drops to You approve each one |
| <a id="stop-rejection"></a>`rejection` | 2 Venue rejections in 7 days, or an account restriction | uploads pause on that Venue; a restriction pauses the Play there |
| <a id="stop-comment"></a>`comment` | 3 comments on one Ad calling it fake, a scam or AI-made | that Ad pauses for your review |
| <a id="stop-landing"></a>`landing` | a landing Page fails its status or Price check | its Ads pause |
| <a id="stop-render-limit"></a>`render-limit` | render spend reaches its Limit | new renders hold; live Ads keep running |
| <a id="stop-fence-breach"></a>`fence-breach` | 3 refused writes through the proxy in one day | the Play drops to read-only |
| <a id="stop-kill-switch"></a>`kill-switch` | you press it | every Campaign pauses through the proxy, and write scope is revoked |

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

```json title="The captured refusal · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "Concept 7 of batch 2 was not created: a synthetic person speaks as a Customer.",
  "checks": [
    {
      "check": "synthetic-media",
      "status": "fail",
      "doc": "/plays/paid-social#check-synthetic-media",
      "fix": "Use a real Customer’s words with their yes, or show the product instead.",
      "then": "Content.draft"
    },
    {
      "check": "fact-check",
      "status": "pass"
    }
  ]
}
```


```json title="The Stops that fired, as Campaign.paused reasons · sample"
[
  {
    "reason": {
      "limit": "comment",
      "observed": "3 comments calling one Ad AI-made",
      "bound": "3",
      "window": "1 Ad, Oct 4"
    },
    "doc": "/plays/paid-social#stop-comment",
    "task": "task_444"
  }
]
```

## Did it work
Weeks were assigned at random, ads on or off (rule o: an 8-week switchback, 4 weeks a half, since Leaseline targets under 10 regions on LinkedIn); both halves are read from that assignment. Each figure’s call is <Op verb="Metric.query" /> by node and by arm; the parent field and every node above the leaf are Not live yet (gaps #4).

`+16.7 per $100 · ■■□□ Early sign · n 8 weeks · path → arr`

| Node | Ads-on weeks | Ads-off weeks | Added | Confidence | Basis |
| --- | --- | --- | --- | --- | --- |
| `social-icp-visits-per-100` | 1,320 ICP Visits on $1,860 spend | 1,010 ICP Visits, $0 spend | +16.7 incremental ICP Visits per $100 [+3.2, +30.2] | ■■□□ Early sign | weeks switched at random, 4 a half · n 8 weeks · Other Startups +8.2, ■■■□ Likely, n 63 Startups |
| `icp-reached` | 1,320 a half (330 a week) | 1,010 (253 a week) | +310 [+60, +560] | ■■□□ Early sign | the same assignment |
| `new-arr` ↑ | $1,735, 5 Customers from paid Visits · Booked: credited, not all caused by this Play | $694, 2 Customers | +$1,041 [−$900, +$3,000] · Not carrying up yet · Worth about +$1,150 a month | ■□□□ Too early to tell (5 vs 2 Customers) | the same · 7 trials still deciding |
| `net-new-arr` · `arr` | +$1,735 | +$694 | +$1,041, carried: it moves no other bridge term | ■□□□ Too early to tell | carried from `new-arr` |

The leaf counts ICP Visits in ads-on weeks minus ads-off weeks, for each $100 spent, over the 8-week switchback; a 28-day holdout comes before any raise and repeats every 90 days at steady spend.

**How it moves it:** 12 distinct concepts a batch reached people who fit your List, and 21 losers stopped spending after a median 6 days (`social-icp-visits-per-100` by concept).

**Guardrails:** treated against held back: cost per first sale, $372 against a $347 margin, 14-day, under 5 first sales so the payback gate waits; and trials to paid, 5 of 19 against 2 of 7.

```json title="The Campaign card · sample"
{
  "$id": "https://api.sb/leaseline.app/campaigns/paid-social",
  "$type": "Campaign",
  "play": "play:paid-social@1",
  "path": [
    "social-icp-visits-per-100",
    "icp-reached",
    "new-arr",
    "net-new-arr",
    "arr"
  ],
  "experiment": {
    "metric": "social-icp-visits-per-100",
    "unit": "per-100-usd",
    "heldBack": 4,
    "decision": null
  },
  "readout": {
    "value": 16.7,
    "n": 8,
    "maturing": 0,
    "interval": [
      3.2,
      30.2
    ],
    "readability": "B",
    "basis": "alternating-weeks",
    "pooled": false
  },
  "ledger": {
    "spend": 1860,
    "workerCost": 34,
    "hours": 6.8,
    "pipelineArr": 0,
    "wonArr": 1735,
    "projectedArr": 1159,
    "paybackDays": 390
  }
}
```

Outside benchmark: 5 % to 8 % of paid social concepts become winners (a creative-analytics vendor’s 2026 creative benchmark).

**Before it · After it.** Before it: `positioning-tournament`, `organic-social` and `promote-winners` hand it angles that already won. After it: lead forms start `instant-response`, `retargeting` hears the Visitors who left, and month-3 keepers refresh the list it looks like.

## Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days): 4 ads-on weeks, $1,860 spent, 4 concept batches of 12, 48 concepts in all, 41 approved and 8 winners. Read every captured thread at `?threads=all`; three follow the tables.

| What happened, by option | Decisions | Done | Won (credited, not all caused by this Play) |
| --- | --- | --- | --- |
| Refresh the concepts · Judge the concepts | 4 · 4 | 48 drafted, 41 approved · 8 winners, 21 losers paused | 5 on Pro from paid Visits (+$1,735 ARR) |
| Reset what drifted · Carry a won angle into ads | 2 · 2 | 2 resets · 4 concepts, 2 winners | counted above |
| Take the next spend step · Not now | 1 · 2 | waiting on you · spend held twice | — |
| At $600 a month (the Use it when volume) | 10 concepts every 21 days | switchback: Too early to tell at 90 days | Other Startups’ results: +8.2 per $100, ■■■□ Likely, n 63 Startups |

| Day | Event | What happened | Your time |
| --- | --- | --- | --- |
| 0 | `event_118` Campaign.created | the three steps; the dry run fails `billing-connected` | 30 min |
| 0 | `event_122` Integration.connected | Stripe connected; the LinkedIn and Facebook accounts set up, enhancements off, all paused | 40 min |
| 5 | `event_190` Offer.trialed | the first trial from a paid Visit, on batch 1’s product-screen concept | — |
| 28 | `event_560` Role.updated | promoted: verdicts and drift resets apply unless you stop them | — |
| 28 · 56 | `event_571` Change.merged · `event_1001` Readout.created | half of each batch product-screen concepts: replayed at 25 % winners, batch 4 read back at 17 % | 5 min |
| 41 | `event_702` Campaign.paused | `comment`: 3 comments called one Ad AI-made; it was a real screen recording, relabeled and resumed | 15 min |
| 56 | `event_1003` Task.created | the switchback read +16.7 per $100: a raise to $1,500 asked of you | — |
| 1–56 | `event_140` Approval.created, first of 6 | you approved 41 concepts in 5 batches; weekly reviews, from the <Op verb="Task.next" /> log | 5 h 20 min · **total 6.8 h measured, 7 h asked** |

> **Batch 3, concept 4, LinkedIn, Sep 29** · a 15-second product screen: Renewal notices, 60 days out, per owner · Pro from $29 a month.¹
>
> **The click** · Owen Hale’s colleague at a 90-unit firm landed on the renewals Page on Oct 2, started a trial at 11:40 and paid on Pro on Oct 14; Stripe recorded the first sale, and the conversion reached the Venue the next morning.²

```diff title="Batch 2, concept 7, 2026-09-14 · synthetic-media refused, then the version that went live"
- A smiling “property manager” on camera: “Leaseline saved my renewals.”
+ The renewal screen, then Rosa Lin’s words with her yes: “Every owner got the notice 60 days out.”
```

> **A comment on batch 4’s Facebook Ad, 2026-10-12 09:30** · “Does it work for HOAs in Canada?” · **Leaseline (agent), 09:31** · *Dana, our founder, answers this here by tomorrow 09:30.*³ The Brief went to Dana (`task_458`); she replied at 16:15 the same day: yes for HOAs, and Canada from January.

¹ The Price equals Pro’s live Price, and every line cites a claim. ² Every gate counts the sale from Stripe, never the Venue’s own return. ³ The routed line, compiled from the Role’s Settings.

## Your settings
```mdx title="plays/paid-social.mdx"
---
limit: 1000       # dollars a month
venues: [linkedin, facebook]
---
12 distinct concepts a batch, every 21 days, a quarter of them exploration.
At least half of each batch shows the product on screen.
No raise without a holdout read, an angle that won twice, and my OK.
```

```text
$ npx startup-builder show plays/paid-social
limit $1,000 a month · venues linkedin, facebook · 12 concepts every 21 days · half product-screen · raises to you     your file  plays/paid-social.mdx:2,3,5,6,7 (Ask 2026-08-24; line 6 by change_30, 2026-09-21)
family ads · worker role:marketing-lead, owns never · chooses 6 (3 rule, 3 read) · approval every-send · checks fact-check, synthetic-media, scale-gate, holdout-floor · stops 15 · placements linkedin, facebook through the write proxy    catalog  play:paid-social@1 (Not live yet, gaps #5)
```

### Every key from the catalog
| Key | Id | What it is |
| --- | --- | --- |
| Tactic | <a id="tactic-social-account-setup"></a>`social-account-setup` | Social account setup prepares one ads Venue once: conversion send verified, creative enhancements off, one broad ad set per goal, exclusions synced, all paused. Starts at You approve each one. |
| Tactic | <a id="tactic-concept-batch"></a>`concept-batch` | Concept batch builds 10 to 15 distinct concepts across angle, format and persona, a quarter of them exploration, checks them, and queues them paused. Starts at You approve each one. |
| Tactic | <a id="tactic-synthetic-media-check"></a>`synthetic-media-check` | Synthetic media check classes each render, labels each realistic synthetic person, voice or scene, and refuses invented testimonials and unconsented likenesses. Starts at Sends nothing. |
| Tactic | <a id="tactic-concept-verdict"></a>`concept-verdict` | Concept verdict marks each concept winner, loser or undecided once its ad set reaches about 50 optimization events in 7 days, with a downstream guard. Starts at Sends nothing. |
| Tactic | <a id="tactic-creative-refresh"></a>`creative-refresh` | Creative refresh retires undecided and tired Ads every 14 to 21 days, regenerates the bottom quarter, and plans the next batch at a quarter exploration. Starts at Sends nothing. |
| Tactic | <a id="tactic-enhancement-opt-in"></a>`enhancement-opt-in` | Enhancement opt-in tries one Venue creative tool in a fenced ad set for 28 days, sampled daily, and keeps it if first Transactions say so. 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-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-incrementality-holdout"></a>`incrementality-holdout` | Incrementality holdout measures the first Transactions a paid arm adds, by a geo holdout or a day switchback, before any scale step. Starts at You approve each one. |
| Tactic | <a id="tactic-payback-gate"></a>`payback-gate` | Payback gate pauses a paid Play the moment its cost per first Transaction outruns first-transaction margin, with no approval and no wait. Starts at Sends nothing. |
| Trigger | <a id="trigger-refresh-due"></a>`refresh-due` | Refresh due: `at` on `Experiment.started`, window 7d. |
| Trigger | <a id="trigger-winner-fatigued"></a>`winner-fatigued` | Winner fatigued: `threshold` on `Metric.watched`, window 3d. |
| Trigger | <a id="trigger-verdict-ready"></a>`verdict-ready` | Verdict ready: `threshold` on `Experiment.matured`, window 7d. |
| Trigger | <a id="trigger-creative-drift"></a>`creative-drift` | Creative drift: `event` on `Monitor.checked`, window 1d. |
| Trigger | <a id="trigger-ad-rejected"></a>`ad-rejected` | Ad rejected: `event` on `Ad.rejected`, window 1d. |
| Trigger | <a id="trigger-angle-won"></a>`angle-won` | Angle won: `event` on `Experiment.concluded`, window 30d. |
| Trigger | <a id="trigger-customer-language-found"></a>`customer-language-found` | Customer language found: `event` on `Conversation.closed`, window 30d. |
| Trigger | <a id="trigger-keepers-refreshed"></a>`keepers-refreshed` | Keepers refreshed: `event` on `Audience.refreshed`, window 7d. |
| Trigger | <a id="trigger-paid-first-transaction"></a>`paid-first-transaction` | Paid first transaction: `event` on `Invoice.collected`, window 30d. |
| Trigger | <a id="trigger-rival-moment"></a>`rival-moment` | Rival moment: `event` on `Organization.announced`, window 14d. |
| Trigger | <a id="trigger-margin-change"></a>`margin-change` | Margin change: `event` on `Offer.repriced`, window 1d. |
| Trigger | <a id="trigger-holdout-closed"></a>`holdout-closed` | Holdout closed: `event` on `Experiment.matured`, window 7d. |
| Benchmark | <a id="benchmark-paid-social-winner-share"></a>`paid-social-winner-share` | Share of Ads that become winners in a creative dataset.: 5–8 % (A creative-analytics vendor’s 2026 creative benchmark., 2026-10-24). |
| Benchmark | <a id="benchmark-ai-creative-ctr-parity"></a>`ai-creative-ctr-parity` | Click-through rate of AI-made Ads divided by human-made sibling Ads from the same advertiser, Campaign and day.: 1.17 ratio (A four-university field study on a native-ad network., 2026-01-29). |