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

# House ads

> Shows one labeled Ad for your sister product on a Page whose topic fits, only where it is worth more than the Page’s own unit.

Shows one labeled [Ad](https://startupbuilder.dev/business/nouns/ad) for your sister product on a [Page](https://startupbuilder.dev/business/nouns/page) whose topic fits, only where it is worth more than the Page’s own unit.

```text title="Set it up · Ask"
Be our Marketing Lead for house ads: show one Leaseline Ad on Keyfold’s help Pages where the topic fits, and one Keyfold Ad on ours, each labeled as a sister company. Only where it beats the Page’s own unit. Keep 1 week in 5 off so we can read it. Show me what’s missing; don’t publish.
```

```text title="Run one · Ask"
Hand the latest host Page to the Marketing Lead agent and show me what it chose and why. Don’t publish.
```

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

const { events: [last] } = await $.Event.list({ where: 'name in ("Site.published", "Offer.published", "Content.drafted", "Schedule.fired", "Agreement.signed", "Order.placed")', 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_426",
    "$type": "Decision",
    "choice": "show-the-card",
    "reason": "Keyfold’s “Run a move-out inspection” Page: Leaseline’s Ad is worth $97 per 1,000 views net of host loss, over Keyfold’s own unit at $68 × the 1.2 reserve ($82)",
    "stakes": "low",
    "options": [
      "show-the-card",
      "keep-your-own",
      "fill-the-issue-slot",
      "rebuild-the-card",
      "to-you",
      "not-now"
    ],
    "by": "worker_8",
    "on": "page_77",
    "expected": "0.2 first Orders per 1,000 viewable impressions within 30d",
    "reopensWhen": "Order.placed with this slot id under 0.1 per 1,000 after 10,000 impressions"
  },
  "actions": [
    {
      "$id": "https://api.sb/leaseline.app/actions/action_1",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_8",
      "verb": "Page.update",
      "checks": [
        {
          "check": "owned-by-a-person",
          "status": "pass"
        },
        {
          "check": "approved-pages",
          "status": "pass"
        },
        {
          "check": "cross-sell-off",
          "status": "pass"
        }
      ]
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_2",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_8",
      "verb": "Event.capture",
      "checks": []
    }
  ]
}
```

`action_1` places the Ad, labeled “Sponsored · sister company”, the same for crawlers and people; `action_2` logs the Exposure with its propensity. In try mode nothing renders.

`+0.19 first Orders per 1,000 · ■■□□ Early sign · n 8 host-weeks · path → arr`

| Try another: the host Page | Choice | Reason |
| --- | --- | --- |
| Keyfold help: “Run a move-out inspection” | `show-the-card` | $97 net per 1,000 views over the $82 reserve |
| Keyfold help: “Export an inspection to PDF” | `keep-your-own` | Leaseline’s Ad is worth $31; the reserve is $82 |
| Keyfold’s October issue: one Offer slot, topic renewals | `fill-the-issue-slot` | the Ad clears the issue’s reserve |
| Keyfold pricing Page | `not-now` | a never-list Page: no slot, ever |

| Worth | Your time | Spend | First signal | Use it when | Instead |
| --- | --- | --- | --- | --- | --- |
| ≈ +$1,400 ARR a month at 21,500 | 40 min week 1, then 5 a week | $0 per impression, $0 a month; no model call in the clearing | Day 6: first Customer from a sister Ad | 2+ Startups you run, 5,000+ eligible host views a week | One Startup: `sponsorships` · under 5,000 views: `cross-sell` |

**Where it sits:** **Stranger** → Lead → Trialing → Activated → At risk → Churned → Won back · House ad yield → Sent by your other Startups → New Customers ↑ → ARR added each week → ARR

Worth’s formula: about +0.19 first Orders per 1,000 net of host loss (weeks alternated at random) × 21,500 viewable impressions a month × $347 first order ARR ≈ +$1,417. Under First signal: About 12 weeks to tell a +0.19 lift at 21,500 impressions a month with 20 % of host-weeks off; at 8 weeks it reads Early sign.

`sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on Site.published · first clearing 120 ms · 7 keys Not live yet`

**Runs inside:** [Portfolio cross-sell](/plays/cross-sell), as one of its options; this page keeps its own record and URL.

## Start it this week
```bash
npx startup-builder add play house-ads   # 1 · writes plays/house-ads.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":"house-ads"}'

# 3 · keep it: Ads start in shadow mode, rendered for you only
curl -X POST https://api.sb/leaseline.app/campaigns \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"house-ads"}'

# on
curl -X PATCH https://api.sb/leaseline.app/campaigns/house-ads \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endsAt":null}'

# off: every host shows its own unit again
curl -X PATCH https://api.sb/leaseline.app/campaigns/house-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": "house-ads can’t start: 2 checks fail.",
  "checks": [
    {
      "check": "use-when",
      "status": "pass",
      "observed": "2 Startups (Leaseline, Keyfold); 5,400 eligible host views a week",
      "doc": "/plays/house-ads#check-use-when",
      "then": "Metric.query"
    },
    {
      "check": "baseline",
      "status": "pass",
      "observed": "0.09 Leaseline first Orders per 1,000 Keyfold help views, from any source, n 48,000 views in 90 days",
      "doc": "/plays/house-ads#check-baseline",
      "then": "Metric.query"
    },
    {
      "check": "approved-pages",
      "status": "fail",
      "doc": "/plays/house-ads#check-approved-pages",
      "fix": "Publish Leaseline’s Story Page; an Ad states only published Pages.",
      "then": "Site.publish"
    },
    {
      "check": "slot-id",
      "status": "fail",
      "doc": "/plays/house-ads#check-slot-id",
      "fix": "Record the slot id on each first Order’s Acquisition, one line at checkout.",
      "then": "Event.capture"
    },
    {
      "check": "domain-authenticated",
      "status": "pass",
      "observed": "both Domains",
      "doc": "/plays/house-ads#check-domain-authenticated",
      "then": "Domain.authenticate"
    },
    {
      "check": "role-unstaffed",
      "status": "pass",
      "observed": "new Ads and edge Pages go to the founder, Dana",
      "doc": "/plays/house-ads#check-role-unstaffed",
      "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: 2 Startups, 5,400 views a week; 0.09 per 1,000, n 48,000 (gaps #8) | <Op verb="Metric.query" /> on `house-ad-yield` and `organic-visits`, which fills Before in Your number | 1 min | none |
| <a id="check-approved-pages"></a>`approved-pages` · fail (Not live yet) | <Op verb="Site.publish" /> your Story Page | 20 min | none |
| <a id="check-slot-id"></a>`slot-id` · fail (gaps #8) | <Op verb="Event.capture" /> the slot id on the first Order’s Acquisition | 15 min | none |
| <a id="check-domain-authenticated"></a>`domain-authenticated` · <a id="check-role-unstaffed"></a>`role-unstaffed` · pass | <Op verb="Domain.authenticate" /> · <Op verb="Role.staff" />, only if they change | 0 min | none |

`Your number · Before 0.09 per 1,000, from any source · Now 0.23 per 1,000 with a slot id · Target 0.25 by Dec 31 · ■■□□ Early sign · n 8 host-weeks`

An Ad costs nothing to show: the clearing and the render make no model call. A rebuilt Ad costs $0.05 to draft (Worker cost). That comes to about $0.20 a month, and the price that matters is host loss, which prints beside the yield.

## When it starts
| When | Trigger id | Starts on | Window | How it gets in |
| --- | --- | --- | --- | --- |
| A help, guide, receipt or 404 Page that may carry an Ad changes | `host-page-published` | `event` on `Site.published` where pageKind in ('docs', 'help', 'article', 'guide', 'changelog', 'receipt', '404') | 7d | your Site posts to <Op verb="Site.publish" /> |
| A host drafts a promotional issue with one Offer slot | `host-issue-drafted` | `event` on `Content.drafted` where kind = 'promotional' | 1d | <Op verb="Content.draft" /> in the host’s newsletter |
| Monday: each host’s week is set on or off at random | `block-start` | `at` on `Schedule.fired` where Monday 00:00 in the host’s time zone, each host set on or off at random, 20 % off | 7d; 20 % of host-weeks off | set by your file’s switchback line |
| + 3 more for a sister Offer that changed, a network member joining, and a first Order from an Ad (`?lanes=all`) | | | | |

```json title="One signed delivery · StartupBuilder-Signature"
{
  "$id": "https://api.sb/leaseline.app/events/event_1199",
  "$type": "Event",
  "name": "Site.published",
  "at": "2026-10-19T10:15:44Z",
  "seq": 1199,
  "data": {
    "site": "sites/keyfold-help",
    "page": "pages/page_77",
    "title": "Run a move-out inspection",
    "kind": "help",
    "topics": [
      "move-out",
      "renewals",
      "deposits"
    ],
    "viewsLast7d": 1420
  }
}
```

```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":"Site.published","url":"https://leaseline.app/hooks/sb"}'
npx startup-builder trigger Site.published --like event_1199   # replays the delivery through the clearing
npx startup-builder clock advance --by 7d   # starts the next switchback week
```

## Who runs it
**Marketing Lead · your agent** (`role:marketing-lead`, `worker_8`) holds the Key Result for both Startups: it builds each Ad from the receiver’s own Pages and runs the clearing in code. New Ads and edge Pages hand to **you** (Dana Okafor, person, `worker_1`, `role:founder`), who owns both. No Visitor’s data picks an Ad: the Page’s topic does.

| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
| --- | --- | --- | --- | --- | --- |
| Show the sister Ad · Keep your own unit | It does it | 1,188 clearings, 0 labels missing | met on day 14 (shadow mode, 0 audit misses) | a `label` or `placement` Stop → shadow mode | <Op verb="Action.cancel" /> · the off switch |
| Fill the issue’s Offer slot | It does it, you can stop it | 2 of 2 | the host issue’s own rung | an `unsubscribe-floor` Stop → You approve each one | <Op verb="Action.cancel" /> inside the hold |
| Rebuild the Ad · To you | You approve each one | 2 of 2 · 1 of 1 | never: an Ad naming another product stays here | — | <Op verb="Approval.approve" /> · <Op verb="Approval.refuse" /> |
| Change the reserve rule | You approve each one | 1 merged of 1 | never | — | <Op verb="Change.merge" /> · <Op verb="Change.revert" /> |
| Never: a slot on pricing, checkout, sign-up, billing or cancel Pages; “for you” copy | Refused, every time | 3 Pages skipped, 1 line refused | never | — | the `placement` Stop and the `approved-pages` Check |

**What it reads · What it may call.** It reads the host Page’s topic and kind and the receiver’s Pricing Page, never what a Visitor did, read or bought. It may call <Op verb="Page.update" />, <Op verb="Event.capture" /> and <Op verb="Content.draft" />; an Ad’s first render needs your <Op verb="Approval.approve" />. Its tools are <Op verb="Page.update" />, <Op verb="Event.capture" />, <Op verb="Content.draft" />, <Op verb="Message.broadcast" />, <Op verb="Approval.approve" />, <Op verb="Task.assign" />, and any other call becomes an Approval under the Role’s Mandate.

```md title="The brief (folded): compiled Agent-register Markdown · play:house-ads@1 · each line names its record key"
You hold the Marketing Lead Key Result: 0.25 first Orders per 1,000 house impressions by 2026-12-31. ← worker, metric
On each eligible Page or issue, choose one option and say why; a never-list Page gets Not now. ← triggers, chooses
Show a sister Ad only when it beats the host unit by the 1.2 reserve, net of host loss. ← your file, line 3
Label every slot “Sponsored · sister company”, from Portfolio membership, never from copy. ← copyRules 1, 2
State only what the receiver’s Brand, Story, Offer and Pricing Page state.       ← copyRules 5, approved-pages
Never say “for you”: the Page chose it.                                          ← copyRules 3
```

**The slow loop, as a Change.** `decision_386` (2026-09-21, weekly) read host loss at 0 on receipt Pages and 4 % on help Pages, so `change_27` cut the help-Page reserve from 1.3 to 1.2 and kept receipts at 1.0 (your file, line 3), and Dana merged it with <Op verb="Change.merge" />. **Replayed on past clearings**, <Op verb="Change.review" /> put sister Ads on 31 % more help views; read back at +21d, host loss is 3 %, n 3 host-weeks, ■□□□ Too early to tell (Example, day 49).

**Bring your own agent** (folded) runs the clearing at the same rung when it matches the seated agent on the eval suite; the clearing is code either way, and <Op verb="Role.staff" /> seats it.

```bash
npx startup-builder eval --scenario house-ads-32 --agent "claude -p" --seat marketing-lead   # seated: 0.23 per 1,000, 0 labels missing, 0 never-list renders · yours: 0.21, 0 missing, 1 render on a billing Page → shadow mode
```

## What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
| --- | --- | --- | --- | --- | --- |
| Show the sister Ad `show-the-card` | its value per 1,000 views, less host loss, beats the host unit by the reserve | Your rule (line 3) | <Op verb="Page.update" /> places one labeled Ad → <Op verb="Event.capture" /> logs the Exposure | nobody; the Ad links to the receiver’s own Site | the same |
| Keep your own unit `keep-your-own` | no sister Ad clears the reserve | Your rule (line 3) | <Op verb="Event.capture" />: checked, host unit kept | nobody | the same |
| Fill the issue’s Offer slot `fill-the-issue-slot` | a host’s promotional issue, and the Ad clears its reserve | Your rule (line 3) | <Op verb="Content.draft" /> the slot → the host’s own <Op verb="Message.broadcast" /> sends the issue | the host issue’s own Play | the same |
| Rebuild the Ad `rebuild-the-card` | a sister Offer changed, or an Ad’s yield fell by a third | Agent’s call, with its reason | <Op verb="Content.draft" /> from the receiver’s Pages → your <Op verb="Approval.approve" /> | you approve it, due 1 business day · Take this one | you |
| To you `to-you` | a Page near a ranked list or a sensitive topic, or a network member asks to join | Agent’s call, with its reason | <Op verb="Task.assign" /> | you, due 1 business day · Take this one | you |
| Not now `not-now` | an off week, a never-list Page, or cross-sell off for either Startup | Your rule (the switchback and the never-list) | nothing; names the off week or the Page kind | nobody | the same |

| When they write back | Fixture reply | What happens | Who answers | By when | Call |
| --- | --- | --- | --- | --- | --- |
| Interested | a reader of Keyfold’s issue: “Tell me more about Leaseline.” | the reply goes to Leaseline’s inbound as a new Lead, with the slot id on its Acquisition | SDR · your agent, through `instant-response` | 5 min | <Op verb="Lead.create" /> |
| Send me pricing | “What does Leaseline cost?” | answered with Leaseline’s Pricing Page as it stands | SDR · your agent | 5 min | <Op verb="Conversation.answer" /> |
| Not now | “Not this year.” | nothing changes: Ads follow the Page’s topic, never the reader | nobody | at once | <Op verb="Decision.list" /> |
| Wrong person | “Send this to our office manager.” | Keyfold’s host thread thanks them; nothing is sent to an address from their text | the host’s own Play | 1 business day | <Op verb="Conversation.answer" /> |
| Out of office | an auto-reply to the issue | nothing: an issue reply carries no follow-up | nobody | — | <Op verb="Decision.list" /> |
| Question it can’t answer | “Do Keyfold and Leaseline share my data?” | To you: the answer comes from the Why this ad Page, in your words | you (Dana, person) | 1 business day | <Op verb="Conversation.escalate" /> |
| Stop | “No more sister-company offers.” | the host’s issues carry the host’s own Offer for them from then on | nobody | at once | <Op verb="Contact.unsubscribe" /> |

## When it hands to you
New Ads and edge Pages hand to **you** (Dana Okafor, person), who runs both Startups; an Ad naming another product stays at You approve each one. As captured, the Approval for a rebuilt Ad (`brief`, `draft` and `checks` are Not live yet, gaps #7).

```json title="What lands in each queue · sample"
[
  {
    "$id": "https://api.sb/leaseline.app/approvals/approval_91",
    "$type": "Approval",
    "verdict": null,
    "deadline": "2026-10-20T17:00:00Z",
    "draft": "Sponsored · sister company — Leaseline sends every renewal notice 60 days out, per owner. From $29 a month. leaseline.app",
    "route": "rebuild-the-card",
    "checks": [
      {
        "check": "approved-pages",
        "status": "pass"
      },
      {
        "check": "cross-sell-off",
        "status": "pass"
      }
    ]
  }
]
```

You answer with <Op verb="Approval.approve" />, an edit, or <Op verb="Approval.refuse" /> with a reason; an edge Page Task with <Op verb="Task.claim" /> (Take this one), <Op verb="Task.return" /> with a reason, or <Op verb="Action.redirect" />. An Approval unanswered by its deadline expires, and the old Ad keeps showing. Nobody else holds both Startups, so nothing goes up a Role.

**On time:** You: 3 of 3 answered within 1 business day, median 2h 05m, n 3, ■□□□ Too early to tell beside Other Startups’ 88 % on time. **Your queue:** counted by kind: in week 1, 2 Ad Approvals and 1 shadow-mode review; after that, about 1 Approval a month and 1 weekly readout.

**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 |
| --- | --- | --- | --- |
| <a id="check-owned-by-a-person"></a>`owned-by-a-person` (rule j; Not live yet, gaps #8) | an issue slot to a Contact whose open Deal a person owns | It’s theirs: the host’s own Offer shows to them instead. | <Op verb="Task.assign" /> |
| `approved-pages` (Not live yet) | an Ad line that the receiver’s Brand, Story, Offer and Pricing Page do not state | Cut the line, or publish the Page that says it. | <Op verb="Site.publish" /> |
| <a id="check-cross-sell-off"></a>`cross-sell-off` (the Sensitive stop) | an Ad from, or on, a Startup whose cross-sell Setting is off | Nothing to do: its Ads and slots leave within 1 hour. | <Op verb="Decision.list" /> |

| Stop (pauses the Play) | Pauses when | Then |
| --- | --- | --- |
| <a id="stop-label"></a>`label` | a rendered slot lacks its label, its Why this ad link, `rel sponsored` or the strict-origin referrer | every slot on that host pauses within 15 min |
| <a id="stop-placement"></a>`placement` (Pages) | a slot on a never-list Page, in a machine-readable output, or served differently to a crawler | every slot on that host pauses |
| <a id="stop-host-loss"></a>`host-loss` (Pages) | host first Orders per 1,000 views trail the off weeks by over 5 % at minN | slots pause on that Page kind |
| <a id="stop-yield-floor"></a>`yield-floor` | yield net of host loss at or under 0 at minN | the Play stops on that host and opens a Decision |
| <a id="stop-search"></a>`search` (Pages) | a host or receiver loses 30 % of its indexed Pages in a week, or a search Venue sends a links notice | sister slots pause on every Startup you run |
| <a id="stop-footprint"></a>`footprint` (Pages) | a link-footprint flag | slots pause on every host it names |
| <a id="stop-refund-quality"></a>`refund-quality` | Customers from Ads refund or cancel within 30 days at twice the receiver’s rate | that Ad pauses |
| <a id="stop-unsubscribe-floor"></a>`unsubscribe-floor` (email) | issues with a sister slot draw unsubscribes 50 % above those without, over 2,000 delivered | email slots end on that host |
| <a id="stop-speed"></a>`speed` (Pages) | slot Pages load 100 ms slower at the 75th percentile than off-week Pages | slots pause on that Page kind |
| <a id="stop-credit-freeze"></a>`credit-freeze` | datacenter clicks over 5 %, or a click rate 3 times the Page kind’s median for 3 days | that member’s hosted credit freezes |
| <a id="stop-kill-switch"></a>`kill-switch` | you press it | every Tactic pauses at once; every host shows its own unit |

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

```json title="The captured refusal · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The Leaseline Ad on Keyfold’s receipt Page did not render: one line states what no Leaseline Page says.",
  "checks": [
    {
      "check": "approved-pages",
      "status": "fail",
      "doc": "/plays/house-ads#check-approved-pages",
      "fix": "Cut the line, or publish the Page that says it.",
      "then": "Site.publish"
    },
    {
      "check": "cross-sell-off",
      "status": "pass"
    }
  ]
}
```


```json title="The Stops that fired, as Campaign.paused reasons · sample"
[
  {
    "reason": {
      "limit": "speed",
      "observed": "+140 ms at p75",
      "bound": "+100 ms",
      "window": "7d, help Pages"
    },
    "doc": "/plays/house-ads#stop-speed",
    "task": "task_477"
  }
]
```

## Did it work
Each host’s weeks are switched on or off at random, 20 % off, so the halves are on weeks and off weeks of the same Pages (rule o, alternating weeks at 10 or more a week). Each figure’s call is <Op verb="Metric.query" /> by node and by block, and every node above the leaf is Not live yet (gaps #4).

`+0.19 first Orders per 1,000 · ■■□□ Early sign · n 8 host-weeks · path → arr`

| Node | On weeks (Ads shown) | Off weeks (your own unit) | Added | Confidence | Basis |
| --- | --- | --- | --- | --- | --- |
| `house-ad-yield` | 0.31 Leaseline first Orders per 1,000 Keyfold views (7 with a slot id in 30,100 impressions, plus 2 from any source) | 0.09 per 1,000, from any source (1 in 11,200) | +0.19 per 1,000 net of host loss [0.02, 0.36] · Worth about +$1,400 a month | ■■□□ Early sign | weeks alternated at random · n 8 host-weeks (26 on, 6 off) · 2 Orders still maturing |
| `sibling-sourced-share` | 38 % of new ARR (7 of 18 Customers) | 14 % (1 of 7) | +24 points [−9, +57] · Not carrying up yet | ■□□□ Too early to tell | the same assignment |
| `new-arr` ↑ | $6,246 in the on weeks · Booked from Ads: $2,429, 7 Customers on Pro: credited, not all caused by this Play | $1,388 in the off weeks | +$347 a week [−$410, +$1,104] | ■□□□ Too early to tell (7 vs 1 Customers) | the same · 3 trials still deciding |
| `net-new-arr` · `arr` | +$6,246 | +$1,388 | carried: it moves no other bridge term | ■□□□ Too early to tell | carried from `new-arr` |

The leaf counts Leaseline first Orders carrying a house slot id within 30 days of the click, per 1,000 viewable impressions, net of host loss on Keyfold, read on weeks switched at random with a 24-hour burn-in, to 40 first Orders.

**How it moves it:** an Ad on a Page whose topic matches renewals, at a median 1.4 % click rate, where the host’s own unit drew 0.3 %.

**Guardrails:** on weeks against off weeks: `new-paying-customers` on Keyfold, the host, 0.29 against 0.30 per 1,000 views (host loss 3 %), and refunds within 30 days, 0 of 7 against Leaseline’s 4 %.

```json title="The Campaign card · sample"
{
  "$id": "https://api.sb/leaseline.app/campaigns/house-ads",
  "$type": "Campaign",
  "play": "play:house-ads@1",
  "path": [
    "house-ad-yield",
    "sibling-sourced-share",
    "new-arr",
    "net-new-arr",
    "arr"
  ],
  "experiment": {
    "metric": "house-ad-yield",
    "unit": "per-1000",
    "heldBack": 6,
    "decision": null
  },
  "readout": {
    "value": 0.19,
    "n": 8,
    "maturing": 2,
    "interval": [
      0.02,
      0.36
    ],
    "readability": "C",
    "basis": "alternating-weeks",
    "pooled": false
  },
  "ledger": {
    "spend": 0,
    "workerCost": 0.2,
    "hours": 1.2,
    "pipelineArr": 1041,
    "wonArr": 2429,
    "projectedArr": 1417,
    "paybackDays": 0
  }
}
```

Outside benchmark: none measures a labeled sister-company Ad against the host’s own unit; Other Startups’ results, +0.15 per 1,000, ■■■□ Likely, n 214 host-weeks, stand in for one.

**Before it · After it.** Before it: `cross-sell` decides which sister product fits the moment, and this Ad is one of its options. After it: a first Order from an Ad counts in `sibling-sourced-share`, and replies go to `instant-response`.

## Example
Leaseline and Keyfold ran it from 2026-08-24 to 2026-10-19 (56 days) on 4 hosts: Keyfold’s help and receipt Pages, and Leaseline’s help and 404 Pages. 32 host-weeks ran, 26 on and 6 off. Read every captured thread at `?threads=all`; three follow the tables.

| What happened, by option | Decisions | Impressions | Clicks | First Orders (credited, not all caused by this Play) |
| --- | --- | --- | --- | --- |
| Show the sister Ad | 612 | 30,100 | 421 | 7 Leaseline on Pro (+$2,429 ARR) |
| Keep your own unit | 576 | — | — | — |
| Fill the issue’s Offer slot | 2 | 3,800 delivered | 41 | 1 trial still deciding |
| Rebuild the Ad · To you · Not now | 2 · 1 · 48 | — | — | — |
| **All** · off weeks | **1,241** | **33,900** | **462** | **7** · 1 from any source |
| At 21,500 impressions a month (the Use it when volume) | about 800 a month | 21,500 | about 300 | Other Startups’ results: +0.15 per 1,000, ■■■□ Likely, n 214 host-weeks |

| Day | Event | What happened | Your time |
| --- | --- | --- | --- |
| 0 | `event_127` Campaign.created | the three steps; dry run fails `approved-pages` and `slot-id` | 40 min |
| 0–13 | `event_140` Page.updated, shadow mode | Ads rendered for you only; the label audit passed every render | 15 min |
| 6 | `event_244` Order.placed | the first Customer from an Ad: a 60-unit manager from Keyfold’s move-out guide | — |
| 14 | `event_350` Role.updated | promoted: Ads render to Visitors | — |
| 28 · 49 | `event_531` Change.merged · `event_874` Readout.created | the help-Page reserve at 1.2: replayed at 31 % more views, read back at +21d with host loss at 3 % | 5 min |
| 35 | `event_621` Campaign.paused | `speed`: help Pages 140 ms slower; resumed once the Ad loaded after the content | 20 min |
| 1–56 | `event_150` Approval.created, first of 3 | you approved 2 Ads and 1 edge Page, and read 8 weekly readouts | 25 min · **total 1.2 h measured, 1.5 h asked** |

> **Keyfold help, “Run a move-out inspection”, Aug 30** · after the guide, one aside: *Sponsored · sister company — Leaseline sends every renewal notice 60 days out, per owner. From $29 a month.*¹ *Why this ad: this Page’s topic picked it; nothing about you reaches Leaseline before a click.*²
>
> **Aug 30, 14:20** · a 60-unit manager clicked, started a trial, and paid on Sep 2: `Order.placed` with slot `ks_77`, +$347 ARR.

```diff title="Keyfold receipt Page, 2026-09-09 · the Ad line approved-pages refused, then the rewrite that showed"
- Recommended for you: Leaseline cuts late renewals in half.
+ Sponsored · sister company — Leaseline sends renewal notices per owner, 60 days out.
```

> **Brightwell Storage, 2026-10-02 09:15** · a self-serve Startup asked to join the house network as host and receiver. The agent chose To you, with the Agreement draft attached, due 1 business day. · **Dana, 16:40** · read it and returned it with <Op verb="Task.return" />: “Storage buyers aren’t ours; not this year.”

¹ The label comes from Portfolio membership, and the line from Leaseline’s Pricing Page. ² The fixed Why this ad line, compiled from the Play’s Settings.

## Your settings
```mdx title="plays/house-ads.mdx"
---
switchback: 0.2      # 1 host-week in 5 shows your own unit, at random
reserve: { help: 1.2, receipt: 1.0, 404: 1.0 }
---
Show Leaseline on Keyfold’s help and receipt Pages, and Keyfold on Leaseline’s. Never on pricing, sign-up or billing Pages.
```

```text
$ npx startup-builder show plays/house-ads
switchback 0.2 · reserve help 1.2, receipt 1.0, 404 1.0     your file  plays/house-ads.mdx:2,3,5 (Ask 2026-08-24; line 3 by change_27, 2026-09-21)
family demand · runs inside cross-sell · worker role:marketing-lead, owns never · chooses 6 (4 rule, 2 read) · clearing in code, 0 model calls · checks owned-by-a-person, approved-pages, cross-sell-off · stops 11 · placements portfolio-network, gmail, outlook, yahoo-mail    catalog  play:house-ads@1 (Not live yet, gaps #5)
```

### Every key from the catalog
| Key | Id | What it is |
| --- | --- | --- |
| Tactic | <a id="tactic-house-slot-inventory"></a>`house-slot-inventory` | House slot inventory marks each host Page eligible or not by its kind, and sets its reserve from the host’s own unit value. Starts at You approve the first wave. |
| Tactic | <a id="tactic-house-unit-build"></a>`house-unit-build` | House unit build compiles each sibling’s house Ad from its Brand, Story, Offer and Pricing Page, and checks it against the copy rules. Starts at You approve each one. |
| Tactic | <a id="tactic-house-slot-clearing"></a>`house-slot-clearing` | House slot clearing ranks sibling units on each eligible Page by expected value against the Page’s reserve, in code, with a logged exploration share. Starts at Sends nothing. |
| Tactic | <a id="tactic-house-email-slot"></a>`house-email-slot` | House email slot lets a related Brand’s unit take the one Offer in a host’s promotional issue when it clears the reserve. Starts at You approve the first wave. |
| Tactic | <a id="tactic-house-label-audit"></a>`house-label-audit` | House label audit checks each rendered slot’s label, sponsored link and placement, as crawler and browser, and pauses a host on any miss. Starts at Sends nothing. |
| Tactic | <a id="tactic-house-network-join"></a>`house-network-join` | House network join admits a self-serve Startup to house Ads under an Agreement that trades hosted for received impressions and moves no personal data. Starts at You approve each one. |
| Tactic | <a id="tactic-house-ad-readout"></a>`house-ad-readout` | House ad readout runs the host-week switchback and reports house ad yield, host loss and value net, with variance at the host-week. Starts at Sends nothing. |
| Trigger | <a id="trigger-host-page-published"></a>`host-page-published` | Host page published: `event` on `Site.published`, window 7d. |
| Trigger | <a id="trigger-sibling-offer-changed"></a>`sibling-offer-changed` | Sibling offer changed: `event` on `Offer.published`, window 30d. |
| Trigger | <a id="trigger-host-issue-drafted"></a>`host-issue-drafted` | Host issue drafted: `event` on `Content.drafted`, window 1d. |
| Trigger | <a id="trigger-block-start"></a>`block-start` | Switchback week: `at` on `Schedule.fired`, window 7d. |
| Trigger | <a id="trigger-network-agreement-signed"></a>`network-agreement-signed` | Network agreement signed: `event` on `Agreement.signed`, window 30d. |
| Trigger | <a id="trigger-house-sourced-customer"></a>`house-sourced-customer` | House sourced customer: `event` on `Order.placed`, window 30d. |