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.
- Step
- Earn referrals
- Channel
- Ads and sponsorships
- Summary
- startupbuilder.dev
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.
# Not live yet (gaps #2)
curl -X POST "https://api.sb/leaseline.app/campaigns?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"play":"house-ads"}'import { $ } from 'startup-builder'
return await $.Campaign.create({ play: 'house-ads' }, { mode: 'try' }) // dry run on the day-0 Startup, 2026-08-24 · Not live yet, gaps #2
npx startup-builder do Campaign.create --json '{"play":"house-ads"}' --try # Not live yet (gaps #2){
"name": "do",
"arguments": {
"code": "return await $.Campaign.create({ play: 'house-ads' }, { mode: 'try' }) // dry run on the day-0 Startup, 2026-08-24 · Not live yet, gaps #2",
"mode": "try"
}
}application/json{
"type": "https: //startupbuilder.dev/ errors/ refused",
"title": "Refused",
"status": 422,
"detail": "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"
},
{
…
Hand the latest host Page to the Marketing Lead agent and show me what it chose and why. Don’t publish.
curl "https://api.sb/leaseline.app/events?where=name%20in%20(%22Site.published%22%2C%20%22Offer.published%22%2C%20%22Content.drafted%22%2C%20%22Schedule.fired%22%2C%20%22Agreement.signed%22%2C%20%22Order.placed%22)&limit=1" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
# Not live yet (gaps #7)
curl -X POST "https://api.sb/leaseline.app/triggers/fire?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"like":"event_1199"}'
# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/decisions?where=on%20%3D%20%22page_77%22" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/actions?where=on%20%3D%20%22page_77%22" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"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 }
npx startup-builder do Event.list --json '{"where":"name in (\"Site.published\", \"Offer.published\", \"Content.drafted\", \"Schedule.fired\", \"Agreement.signed\", \"Order.placed\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_1199"}' --try # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"page_77\""}' # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"page_77\""}' # Not live yet (gaps #7){
"name": "do",
"arguments": {
"code": "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\nconst { subject } = await $.Trigger.fire({ like: last.$id }, { mode: 'try' }) // the same Event again, in try mode · Not live yet, gaps #7\nconst { decisions: [decision] } = await $.Decision.list({ where: `on = \"${subject}\"` }) // what it chose, and why · Not live yet, gaps #7\nconst { actions } = await $.Action.list({ where: `on = \"${subject}\"` }) // what it did, with its checks · Not live yet, gaps #7\nreturn { decision, actions }",
"mode": "try"
}
}application/json{
"decision": {
"$id": "https: //api.sb/ leaseline.app/ decisions/ decision_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"
…
sample · 4 Not live yetPreviewOn PATCH /{namespace}/campaigns/{id}Off PATCH /{namespace}/campaigns/{id}Take this one POST /{namespace}/tasks/{task}/claim
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,
| 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 |
- 40 min week 1, then 5 a week
- Your time
- Day 6: first Customer from a sister Ad
- First signal
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/
Runs inside: Portfolio cross-sell, as one of its options; this page keeps its own record and URL.
Start it this week
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"}' | Check | Fix | Then |
|---|---|---|
use-when pass · Not live yet, gaps #8 | 2 Startups (Leaseline, Keyfold); 5,400 eligible host views a week | POST /{namespace}/metrics/query |
baseline pass · Not live yet, gaps #8 | 0.09 Leaseline first Orders per 1,000 Keyfold help views, from any source, n 48,000 views in 90 days | POST /{namespace}/metrics/query |
approved-pages fail · Not live yet, gaps #8 | Publish Leaseline’s Story Page; an Ad states only published Pages. | POST /{namespace}/sites/{site}/publish |
slot-id fail | Record the slot id on each first Order’s Acquisition, one line at checkout. | POST /{namespace}/events/capture |
domain-authenticated pass | both Domains | POST /{namespace}/domains/{domain}/authenticate |
role-unstaffed pass · Not live yet, gaps #8 | new Ads and edge Pages go to the founder, Dana | POST /{namespace}/roles/{role}/staff |
JSON
{
"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"
}
]
}It says no, says why, and names the fix.
| Check | Fix (one call) | Your time | Wait |
|---|---|---|---|
use-when · baseline · pass: 2 Startups, 5,400 views a week; 0.09 per 1,000, n 48,000 (gaps #8) | POST /{namespace}/metrics/query on house-ad-yield and organic-visits, which fills Before in Your number | 1 min | none |
approved-pages · fail (Not live yet) | POST /{namespace}/sites/{site}/publish your Story Page | 20 min | none |
slot-id · fail (gaps #8) | POST /{namespace}/events/capture the slot id on the first Order’s Acquisition | 15 min | none |
domain-authenticated · role-unstaffed · pass | POST /{namespace}/domains/{domain}/authenticate · POST /{namespace}/roles/{role}/staff, only if they change | 0 min | none |
Your number · Before 0.09 per 1,
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 POST /{namespace}/sites/{site}/publish |
| A host drafts a promotional issue with one Offer slot | host-issue-drafted | event on Content.drafted where kind = 'promotional' | 1d | POST /{namespace}/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=) |
{
"$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
}
}# 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 weekWho runs it
Marketing Lead · your agent (role:, 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:), 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 | POST /{namespace}/actions/{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 | POST /{namespace}/actions/{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 | — | POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse |
| Change the reserve rule | You approve each one | 1 merged of 1 | never | — | POST /{namespace}/changes/{change}/merge · POST /{namespace}/changes/{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 PATCH /{namespace}/pages/{id}, POST /{namespace}/events/capture and POST /{namespace}/content/draft; an Ad’s first render needs your POST /{namespace}/approvals/{approval}/approve. Its tools are PATCH /{namespace}/pages/{id}, POST /{namespace}/events/capture, POST /{namespace}/content/draft, POST /{namespace}/messages/{message}/broadcast, POST /{namespace}/approvals/{approval}/approve, POST /{namespace}/tasks/{task}/assign, and any other call becomes an Approval under the Role’s Mandate.
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 POST /{namespace}/changes/{change}/merge. Replayed on past clearings, POST /{namespace}/changes/{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 POST /{namespace}/roles/{role}/staff seats it.
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) | PATCH /{namespace}/pages/{id} places one labeled Ad → POST /{namespace}/events/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) | POST /{namespace}/events/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) | POST /{namespace}/content/draft the slot → the host’s own POST /{namespace}/messages/{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 | POST /{namespace}/content/draft from the receiver’s Pages → your POST /{namespace}/approvals/{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 | POST /{namespace}/tasks/{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 | POST /{namespace}/leads |
| Send me pricing | “What does Leaseline cost?” | answered with Leaseline’s Pricing Page as it stands | SDR · your agent | 5 min | POST /{namespace}/conversations/{conversation}/answer |
| Not now | “Not this year.” | nothing changes: Ads follow the Page’s topic, never the reader | nobody | at once | GET /{namespace}/decisions |
| 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 | POST /{namespace}/conversations/{conversation}/answer |
| Out of office | an auto-reply to the issue | nothing: an issue reply carries no follow-up | nobody | — | GET /{namespace}/decisions |
| 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 | POST /{namespace}/conversations/{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 | POST /{namespace}/contacts/{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).
[
{
"$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 POST /{namespace}/approvals/{approval}/approve, an edit, or POST /{namespace}/approvals/{approval}/refuse with a reason; an edge Page Task with POST /{namespace}/tasks/{task}/claim (Take this one), POST /{namespace}/tasks/{task}/return with a reason, or POST /{namespace}/actions/{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: POST /{namespace}/deals/{deal}/assign sets owner to the receiving person, the agent is never a Deal’s owner, and Meetings land on the owner’s calendar under the owner’s name.
When it won’t send
| Check (refuses one send) | Refuses | Fix | Then |
|---|---|---|---|
owned-by-a-person (rule j; Not live yet, gaps #8) | an issue slot to a Contact whose open Deal a person owns | It’s theirs: the host’s own Offer shows to them instead. | POST /{namespace}/tasks/{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. | POST /{namespace}/sites/{site}/publish |
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. | GET /{namespace}/decisions |
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.
| Check | Fix | Then |
|---|---|---|
approved-pages fail · Not live yet, gaps #8 | Cut the line, or publish the Page that says it. | POST /{namespace}/sites/{site}/publish |
cross-sell-off pass | — | GET /{namespace}/decisions |
JSON
{
"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"
}
]
}It says no, says why, and names the fix.
[
{
"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 POST /{namespace}/metrics/query by node and by block, and every node above the leaf is Not live yet (gaps #4).
+0.19 first Orders per 1,
- Chose
- show-the-card
- Why
- 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)
- From
- show-the-card · keep-your-own · fill-the-issue-slot · rebuild-the-card · to-you · not-now
- Expects
- 0.2 first Orders per 1,000 viewable impressions within 30d
- Reopens when
- Order.placed with this slot id under 0.1 per 1,000 after 10,000 impressions
- By
- worker_8
JSON
{
"$id": "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"
}| 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 %.
{
"$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=; 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.placedwith slotks_77, +$347 ARR.
- 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
POST /{namespace}/tasks/{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
---
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. $ 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
Compiled, not written: edit the Play record house-ads and its Leaseline run, then regenerate. Every step runs from a sentence or a line of code. In your words: the CRM words you know, and where each lives here.