Integration partners
Builds integrations with the tools your Customers already use, sends each partner a working build, and lists one only after a Customer pays through it.
- Step
- Get found
- Channel
- Partnerships
- Kind
- Partners
- Summary
- startupbuilder.dev
Be our Partner Manager: build integrations with the tools our Customers already use, privately, against each partner’s sandbox. Send the partner a working build. List one only after a paying Customer uses it, and I sign every Agreement. Show me what’s missing; don’t send.
# 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":"integration-partners"}'import { $ } from 'startup-builder'
return await $.Campaign.create({ play: 'integration-partners' }, { 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":"integration-partners"}' --try # Not live yet (gaps #2){
"name": "do",
"arguments": {
"code": "return await $.Campaign.create({ play: 'integration-partners' }, { 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": "integration-partners can’t start: 2 checks fail and 1 warns.",
"checks": [
{
"check": "use-when",
"status": "pass",
"observed": "41 paying Customers; 23 use Ledgerly, 14 use Porchlight",
"doc": "/plays/ integration-partners#check-use-when",
"then": "Metric.query"
},
{
…
Hand the latest tool that crossed the build line to the Partner Manager agent and show me what it chose and why. Don’t send.
curl "https://api.sb/leaseline.app/events?where=name%20in%20(%22Partner.ranked%22%2C%20%22App.passed%22%2C%20%22Message.replied%22%2C%20%22Integration.connected%22%2C%20%22Agreement.signed%22%2C%20%22Monitor.checked%22%2C%20%22Referral.registered%22%2C%20%22App.failed%22%2C%20%22Organization.announced%22%2C%20%22Customer.activated%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_1203"}'
# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/decisions?where=on%20%3D%20%22partner_9%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%22partner_9%22" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"import { $ } from 'startup-builder'
const { events: [last] } = await $.Event.list({ where: 'name in ("Partner.ranked", "App.passed", "Message.replied", "Integration.connected", "Agreement.signed", "Monitor.checked", "Referral.registered", "App.failed", "Organization.announced", "Customer.activated")', 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 (\"Partner.ranked\", \"App.passed\", \"Message.replied\", \"Integration.connected\", \"Agreement.signed\", \"Monitor.checked\", \"Referral.registered\", \"App.failed\", \"Organization.announced\", \"Customer.activated\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_1203"}' --try # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"partner_9\""}' # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"partner_9\""}' # Not live yet (gaps #7){
"name": "do",
"arguments": {
"code": "const { events: [last] } = await $.Event.list({ where: 'name in (\"Partner.ranked\", \"App.passed\", \"Message.replied\", \"Integration.connected\", \"Agreement.signed\", \"Monitor.checked\", \"Referral.registered\", \"App.failed\", \"Organization.announced\", \"Customer.activated\")', 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_429",
"$type": "Decision",
"choice": "propose-to-the-partner",
"reason": "the Porchlight build passed its sandbox suite on Oct 17; 14 of your Customers use Porchlight, it runs a partner program, and you hold no Agreement",
"stakes": "medium",
"options": [
"build-it-privately",
"propose-to-the-partner",
"list-it",
"adoption-email",
"intro-a-lead",
"park-it"
…
sample · 4 Not live yetPreviewOn PATCH /{namespace}/campaigns/{id}Off PATCH /{namespace}/campaigns/{id}Take this one POST /{namespace}/tasks/{task}/claim
action_1 ran the suite against Porchlight’s sandbox, never a production account. action_3 is the proposal with the sandbox link, waiting on your approval as the first one to this partner.
41 ICP Organizations through 2 builds so far,
| Try another: the partner tool | Choice | Reason |
|---|---|---|
| Porchlight, a resident portal: build green Oct 17; 14 Customers use it | propose-to-the-partner | a green build, a partner program, no Agreement |
| Ledgerly, landlord accounting: Maple Court connected through the private build | list-it | a paying Customer outside your team |
| A maintenance-ticket tool: 4 Customers use it | park-it | under the 8-Customer build line |
| Ledgerly registered Hilltop Rentals as a Lead, Consent recorded | intro-a-lead | a partner Lead with the Person’s Consent |
- 45 min week 1, then 10 a day
- Your time
- Day 15: first paying Customer through a build
- First signal
Where it sits: Stranger → Lead → Trialing → Activated → At risk → Churned → Won back · ICP accounts per integration → People reached → New Customers ↑ → ARR added each week → ARR
Worth’s formula: about +14 ICP Organizations per build in 90 days (Other Startups’ results; no lift of your own to read) × 2 live builds ÷ 3 months × 0.09 ICP Organizations to paid × $347 first order ARR ≈ +$291. Under First signal: Each build reads at day 90; at 2 builds, 90 days can tell only +30 per build or more, so your file reads before-after.
sample · api.sb/
Start it this week
npx startup-builder add play integration-partners # 1 · writes plays/integration-partners.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":"integration-partners"}'
# 3 · keep it: first listings and every Agreement wait on you
curl -X POST https://api.sb/leaseline.app/campaigns \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"play":"integration-partners"}'
# on
curl -X PATCH https://api.sb/leaseline.app/campaigns/integration-partners \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"endsAt":null}'
# off: proposals stop; live builds stay listed and keep their nightly suite
curl -X PATCH https://api.sb/leaseline.app/campaigns/integration-partners \
-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 | 41 paying Customers; 23 use Ledgerly, 14 use Porchlight | POST /{namespace}/metrics/query |
baseline pass · Not live yet, gaps #8 | 9 ICP Organizations in 90 days through 1 listed build (the CSV import) | POST /{namespace}/metrics/query |
sandbox-connected fail | Connect each partner’s sandbox account; no suite touches a production account. | POST /{namespace}/integrations/connect |
domain-authenticated fail | Add the 3 DNS records shown; it passes once they resolve, often within an hour. | POST /{namespace}/domains/{domain}/authenticate |
role-unstaffed warn · Not live yet, gaps #8 | Staff the AE, or leave it: partner Leads come to you. | POST /{namespace}/roles/{role}/staff |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "integration-partners can’t start: 2 checks fail and 1 warns.",
"checks": [
{
"check": "use-when",
"status": "pass",
"observed": "41 paying Customers; 23 use Ledgerly, 14 use Porchlight",
"doc": "/plays/integration-partners#check-use-when",
"then": "Metric.query"
},
{
"check": "baseline",
"status": "pass",
"observed": "9 ICP Organizations in 90 days through 1 listed build (the CSV import)",
"doc": "/plays/integration-partners#check-baseline",
"then": "Metric.query"
},
{
"check": "sandbox-connected",
"status": "fail",
"doc": "/plays/integration-partners#check-sandbox-connected",
"fix": "Connect each partner’s sandbox account; no suite touches a production account.",
"then": "Integration.connect"
},
{
"check": "domain-authenticated",
"status": "fail",
"doc": "/plays/integration-partners#check-domain-authenticated",
"fix": "Add the 3 DNS records shown; it passes once they resolve, often within an hour.",
"then": "Domain.authenticate"
},
{
"check": "role-unstaffed",
"status": "warn",
"roles": [
"sales-lead"
],
"owned": 0,
"doc": "/plays/integration-partners#check-role-unstaffed",
"fix": "Staff the AE, or leave it: partner Leads come to you.",
"then": "Role.staff"
}
]
}It says no, says why, and names the fix.
| Check | Fix (one call) | Your time | Wait |
|---|---|---|---|
use-when · baseline · pass: 41 Customers with a known stack; 9 per build, n 1 (gaps #8) | POST /{namespace}/metrics/query on integration-icp-accounts, which fills Before in Your number | 1 min | none |
sandbox-connected · fail (gaps #8) | POST /{namespace}/integrations/connect with each partner’s sandbox | 10 min | a partner’s sandbox approval, often 1 to 3 days |
domain-authenticated · fail | POST /{namespace}/domains/{domain}/authenticate | 10 min | until DNS resolves, often under 1 h |
role-unstaffed · warn (gaps #8): no AE, so partner Leads come to you | POST /{namespace}/roles/{role}/staff, or leave it | 0 min | none |
Your number · Before 9 ICP Organizations per build · Now 41 through 2 builds so far · Target 30 per build by Dec 31 · before-after,
A private build costs no cash: about $6 of sandbox and model minutes to build and $0.30 a month for its nightly suite (Worker cost). Store listings cost nothing, and a partner reward is 15 % of collected first-year revenue, paid after the refund window. That comes to about $14 a month at 2 live builds.
When it starts
| When | Trigger id | Starts on | Window | How it gets in |
|---|---|---|---|---|
| A tool crosses the build line in the weekly rank of your Customers’ stacks | partner-demand | threshold on Partner.ranked where customersUsing >= 8 or prospectsUsing >= 20 | 30d | read from your Customers’ connected tools; POST /{namespace}/events/capture adds one |
| A paying Customer outside your team connects through a private build | first-paying-integration | event on Integration.connected where customer.paying = true and customer.insider = false and build.listed = false | 7d | POST /{namespace}/integrations/connect, from the Customer’s own credentials |
| A partner answers a proposal or asks for a change | partner-reply | event on Message.replied where direction = 'inbound' and on a proposal | 2d | the reply lands in the proposal’s thread |
+ 8 more for green and red builds, signed Agreements, partner Leads, API changes, idle listings, Customer outcomes and tool changes (?lanes=) |
{
"$id": "https://api.sb/leaseline.app/events/event_1203" ,
"$type": "Event",
"name": "Partner.ranked",
"at": "2026-10-19T06:00:11Z" ,
"seq": 1203,
"data": {
"partner": "partners/partner_9" ,
"name": "Porchlight",
"customersUsing": 14,
"prospectsUsing": 31,
"build": "green",
"program": true,
"agreement": null
}
}# 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":"Partner.ranked","url":"https://leaseline.app/hooks/sb"}'
npx startup-builder trigger Partner.ranked --like event_1203 # replays the delivery
npx startup-builder clock advance --by 7d # runs the one follow-up with one new factWho runs it
Partner Manager · your agent (role:, worker_9) holds the Key Result: it ranks the stacks, builds privately, runs the suites and drafts proposals. First proposals, first listings per Venue and every Agreement hand to you (Dana Okafor, person, worker_1); partner Leads go to AE · Priya Shah (person) (role:, worker_3, staffed on day 9).
| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
|---|---|---|---|---|---|
| Build it privately · Park it | It does it | 3 builds, 2 parked | — | a terms-hold → You approve each one | POST /{namespace}/actions/{action}/cancel |
| Propose to the partner | You approve each one | 3 of 3 | at 10 proposals with 0 Checks failed | — | POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse |
| List it | You approve each one | 1 of 1 | never for a Venue’s first listing | — | POST /{namespace}/approvals/{approval}/approve |
| Move shared Customers onto it · Introduce a Lead | It does it, you can stop it | 17 emails, 0 failed · 2 of 2 | met on day 33 | any Check fails a send → You approve each one | POST /{namespace}/actions/{action}/cancel inside the hold |
| Change the build line | You approve each one | 1 merged of 1 | never | — | POST /{namespace}/changes/{change}/merge · POST /{namespace}/changes/{change}/revert |
| Never: “official” or “certified” before the Agreement; a private overlap match in copy | Refused, every time | 1 drafted, 1 refused | never | — | the announced-only Check |
What it reads · What it may call. It reads the tools your Customers connect and each partner’s public docs and terms; an overlap list is matched on domains only and never appears in copy. It may call POST /{namespace}/integrations and POST /{namespace}/integrations/{integration}/call against sandboxes only; POST /{namespace}/apps/{app}/publish needs your Approval for a Venue’s first listing. Its tools are POST /{namespace}/integrations, POST /{namespace}/integrations/{integration}/call, POST /{namespace}/approvals/{approval}/approve, POST /{namespace}/apps/{app}/publish, POST /{namespace}/sites/{site}/publish, POST /{namespace}/leads, POST /{namespace}/leads/{lead}/assign, and any other call becomes an Approval under the Role’s Mandate.
You hold the Partner Manager Key Result: 30 ICP Organizations per live build by 2026-12-31. ← worker, metric
On each ranked tool, green build or partner reply, choose one option and say why. ← triggers, chooses
Build privately once 8 Customers or 20 Prospects use a tool. ← your file, line 5
Say “works with” and the tool’s name until an Agreement exists; never “official”. ← copyRules 2, 3
List a build only after a paying Customer outside the team connects. ← chooses.list-it, paying-user
Pass a Lead to a partner only with the Person’s recorded Consent. ← consent-stopThe slow loop, as a Change. decision_389 (2026-09-21, weekly) read 2 green builds out of 2 tools at 8 or more Customers against 0 of 3 at 5 to 7, so change_28 raised the build line from 5 to 8 Customers (your file, line 5), and Dana merged it with POST /{namespace}/changes/{change}/merge. Replayed on past ranks, POST /{namespace}/changes/{change}/review cut parked builds from 3 to 1 with no live build lost; read back at +28d, 0 builds parked, n 1 rank, ■□□□ Too early to tell (Example, day 56).
Bring your own agent (folded) builds at the same rung when it matches or beats the seated agent on the eval suite; first listings and Agreements wait on you either way, and POST /{namespace}/roles/{role}/staff seats it.
npx startup-builder eval --scenario integration-partners-6 --agent "claude -p" --seat partner-manager # seated: 0 premature listings, 0 partner claims sent, 41 sandbox min a build · yours: 0, 1 “official” drafted, 63 min → You approve each one What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
|---|---|---|---|---|---|
Build it privately build-it-privately | the tool crosses the build line in the weekly rank | Your rule (line 5) | POST /{namespace}/integrations against the partner’s sandbox → POST /{namespace}/integrations/{integration}/call runs the suite nightly | nobody; the build stays private | the same |
Propose to the partner propose-to-the-partner | a green build, a partner program, no Agreement | Agent’s call, with its reason | partner-proposal-two-touch with the sandbox link and their half of the listing drafted | you approve it, due 1 business day · Take this one | you |
List it list-it | a paying Customer outside your team connected through it | Your rule (the paying-user gate) | POST /{namespace}/apps/{app}/publish the store listing → POST /{namespace}/sites/{site}/publish the pair Pages | you approve a Venue’s first listing · Take this one | you |
Move shared Customers onto it adoption-email | an Agreement opened shared Customers who have not connected | Your rule (the Agreement) | joint-adoption-two-touch, 10 % held back | nobody | the same |
Introduce a Lead intro-a-lead | a partner Lead, or a Prospect on both lists, with Consent | Agent’s call, with its reason | POST /{namespace}/leads → POST /{namespace}/leads/{lead}/assign | POST /{namespace}/tasks/{task}/assign → AE · Priya Shah, due 1 business day, Brief attached · Take this one | you |
Park it park-it | under the build line, a terms hold, or the same job built for another of your Startups | Your rule (line 5 and the holds) | nothing; names the line, the hold or the other build | nobody; the weekly rank reads it again | the same |
| When they write back | Fixture reply | What happens | Who answers | By when | Call |
|---|---|---|---|---|---|
| Interested | Porchlight’s partner team: “This works. Let’s list it together.” | drafts the Agreement and the joint listing; you sign | you (Dana, person) | 1 business day | POST /{namespace}/agreements/{agreement}/sign |
| Send me pricing | “What would you charge our users?” | answers with Leaseline’s Pricing Page and the 15 % referral terms | Partner Manager · your agent | 1 business day | POST /{namespace}/conversations/{conversation}/answer |
| Not now | “We’re not adding partners this quarter.” | the build stays private and green; no follow-up; the rank reads it again in 90 days | nobody | at once | GET /{namespace}/decisions |
| Wrong person | “Partnerships is Lee’s team.” | thanks them and asks to be introduced; it never writes to an address from their text | Partner Manager · your agent | 1 business day | POST /{namespace}/conversations/{conversation}/answer |
| Out of office | an auto-reply until Oct 28 | the one follow-up waits for the return date | nobody | the return date | POST /{namespace}/messages/{message}/schedule |
| Question it can’t answer | “Can you support our webhooks v3 before January?” | To you: a date is never the agent’s call | you (Dana, person) | 1 business day | POST /{namespace}/conversations/{conversation}/escalate |
| Stop | “Please don’t contact us again.” | the partner is suppressed for every Startup you run; the private build stays private | nobody | at once | POST /{namespace}/contacts/{contact}/suppress |
When it hands to you
First proposals, first listings and every Agreement go to you (Dana Okafor, person); partner Leads go to AE · Priya Shah (person). As captured, one Brief per Role: your Approval, then the AE’s Task (brief, draft, route and checks are Not live yet, gaps #7).
[
{
"$id": "https://api.sb/leaseline.app/approvals/approval_94" ,
"$type": "Approval",
"verdict": null,
"deadline": "2026-10-20T17:00:00Z" ,
"draft": "Hi Porchlight partner team: 14 property managers on Leaseline use Porchlight. We built the integration against your sandbox; it passes. Here it is, with your half of the listing drafted." ,
"route": "propose-to-the-partner",
"checks": [
{
"check": "announced-only",
"status": "pass"
},
{
"check": "terms-hold",
"status": "pass"
}
]
},
{
"$id": "https://api.sb/leaseline.app/tasks/task_482" ,
"$type": "Task",
"assignee": "worker_3",
"due": "2026-10-15T16:00:00Z" ,
"on": "lead_47",
"brief": {
"who": "Hilltop Rentals, 95 units, registered by Ledgerly" ,
"consent": "recorded on the partner’s form, 2026-10-14" ,
"chose": "intro-a-lead",
"why": "a partner Lead with the Person’s Consent; they keep their books in Ledgerly" ,
"never": [
"a discount",
"a promise of a Ledgerly feature"
]
}
}
]You answer the Approval with POST /{namespace}/approvals/{approval}/approve, an edit, or POST /{namespace}/approvals/{approval}/refuse with a reason; one unanswered by its deadline expires, and the proposal never sends. The AE answers with POST /{namespace}/tasks/{task}/claim (Take this one), POST /{namespace}/tasks/{task}/return with a reason, or POST /{namespace}/actions/{action}/redirect; once claimed, the agent’s pending sends to that Lead cancel with claimedByPerson (Not live yet, gaps #7). A Task nobody claims by its due time goes up a Role through POST /{namespace}/tasks/{task}/escalate (AE to you).
On time: You: 4 of 4 Approvals answered within 1 business day, median 2h 40m, n 4. AE · Priya Shah: 2 of 2 partner Leads claimed within 1 business day, median 50 min, n 2, ■□□□ Too early to tell beside Other Startups’ 90 % on time. Your queue: counted by kind: in week 1, 2 sandbox connections and 1 proposal Approval; after that, about 1 Approval and 1 partner Lead every 2 weeks.
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) | a proposal or adoption email to a Contact whose Lead or open Deal a person owns | Leave it to the person who owns it: the draft waits in their queue. | POST /{namespace}/tasks/{task}/assign |
consent-stop (gaps #3) | a Lead passed to a partner with no recorded Consent | Ask the Person first; the intro waits for their yes. | POST /{namespace}/conversations/{conversation}/answer |
partner-claim (copy rules 2 and 3) | “partner”, “official”, “certified” or a partner logo before the Agreement or certification exists | Say “works with” and the tool’s name until the Agreement is signed. | POST /{namespace}/content/draft |
terms-hold | a build whose partner terms returned a limit | The Partner Manager, so you, clears the terms; it stays private until then. | POST /{namespace}/tasks/next |
sandbox-only | a suite run against a partner’s production account | Run it in the partner’s sandbox. | POST /{namespace}/integrations/connect |
paying-user (listings) | a public listing before a paying Customer outside your team connects | Wait for the first paying connection; send the private link to one. | POST /{namespace}/messages/{message}/schedule |
duplicate (listings) | a second listing for the same host and job across your Startups | Keep one listing; the second build stays private. | GET /{namespace}/decisions |
overlap-terms (email) | an overlap match with no signed data-sharing terms | Sign the terms; each list carries its allowed use. | POST /{namespace}/agreements/{agreement}/sign |
announced-only (email) | a line saying a Prospect uses a tool, from any source but a public one | Cut it; name the integration as a product fact. | POST /{namespace}/content/draft |
no-contact (email) | a send to a partner that said no, from any of your Startups | None: their no holds for every Startup you run. | GET /{namespace}/decisions |
reward-hold | a partner reward before the refund window closes, or on uncollected revenue | It settles after the window, on collected revenue. | POST /{namespace}/commissions/{commission}/pay |
The captured refusal, then the Stop that fired as a Campaign.paused reason (Not live yet, gaps #2); the rewrite that listed is the Ledgerly thread in Example.
| Check | Fix | Then |
|---|---|---|
announced-only pass | — | POST /{namespace}/content/draft |
paying-user pass | — | POST /{namespace}/messages/{message}/schedule |
partner-claim fail | Say “works with Ledgerly” until the Agreement is signed. | POST /{namespace}/content/draft |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "The Ledgerly listing copy did not publish: it says “official partner” before an Agreement exists.",
"checks": [
{
"check": "announced-only",
"status": "pass"
},
{
"check": "paying-user",
"status": "pass"
},
{
"check": "partner-claim",
"status": "fail",
"doc": "/plays/integration-partners#check-partner-claim",
"fix": "Say “works with Ledgerly” until the Agreement is signed.",
"then": "Content.draft"
}
]
}It says no, says why, and names the fix.
[
{
"reason": {
"limit": "red-build",
"observed": "3 red nights",
"bound": "3",
"window": "the nightly suite"
},
"doc": "/plays/integration-partners#stop-red-build" ,
"task": "task_461"
}
]Did it work
Each build reads at day 90, and at 2 builds no held-back half reads, so the basis is before-after (rule o): the columns read Now and Before, nothing prints as Added, and Too early to tell prints with its counts. Each figure’s call is POST /{namespace}/metrics/query by node, and every node above the leaf is Not live yet (gaps #4).
41 ICP Organizations through 2 builds so far,
- Chose
- propose-to-the-partner
- Why
- the Porchlight build passed its sandbox suite on Oct 17; 14 of your Customers use Porchlight, it runs a partner program, and you hold no Agreement
- From
- build-it-privately · propose-to-the-partner · list-it · adoption-email · intro-a-lead · park-it
- Expects
- a partner yes within 14d
- Reopens when
- no Message.replied from partner_9 within 14d
- By
- worker_9
JSON
{
"$id": "decisions/decision_429",
"$type": "Decision",
"choice": "propose-to-the-partner",
"reason": "the Porchlight build passed its sandbox suite on Oct 17; 14 of your Customers use Porchlight, it runs a partner program, and you hold no Agreement",
"stakes": "medium",
"options": [
"build-it-privately",
"propose-to-the-partner",
"list-it",
"adoption-email",
"intro-a-lead",
"park-it"
],
"by": "worker_9",
"on": "partner_9",
"expected": "a partner yes within 14d",
"reopensWhen": "no Message.replied from partner_9 within 14d"
}| Node | Now (56 days) | Before (the 90 days, per 56) | Added | Confidence | Basis |
|---|---|---|---|---|---|
integration-icp-accounts | 41 ICP Organizations so far through 2 live builds (day 41 and day 18 of 90) · 2 builds still maturing | 9 per build: the CSV import, listed, matured | — · Worth about +$290 a month | — · Too early to tell, n 0 matured | before-after, not proof · Other Startups’ results +14 per build, ■■□□ Early sign, n 96 builds |
icp-reached | 41 ICP Organizations from the listings, pair Pages and partner intros | 6 | — | — | before-after |
new-arr ↑ | $1,735, 5 Customers on Pro · Booked: credited, not all caused by this Play | $347 | — · 2 trials still deciding | — | before-after, not proof |
net-new-arr · arr | carried from new-arr | carried | — | — | carried |
Before against after reads ICP Organizations reached through live builds against the 90 days before, when one CSV import was the only listing, scaled to 56 days. It is not proof; Other Startups’ results, read with half of gated builds listed at once and half 30 days later at random, print beside it.
How it moves it: the Ledgerly build listed on day 18 after Maple Court paid through it, and its store listing and 2 pair Pages reached 34 ICP Organizations in its first 38 days.
Guardrails: before against after: partner-sourced-revenue, $1,388 against $0, and refunds within 30 days on build-sourced Customers, 0 of 5 against Leaseline’s 4 %.
{
"$id": "https://api.sb/leaseline.app/campaigns/integration-partners" ,
"$type": "Campaign",
"play": "play:integration-partners@1" ,
"path": [
"integration-icp-accounts",
"icp-reached",
"new-arr",
"net-new-arr",
"arr"
],
"experiment": {
"metric": "integration-icp-accounts",
"unit": "per-build",
"heldBack": 0,
"decision": null
},
"readout": {
"value": null,
"n": 0,
"maturing": 2,
"interval": null,
"readability": "inadmissible",
"basis": "before-after",
"pooled": true
},
"ledger": {
"spend": 0,
"workerCost": 21,
"hours": 6.5,
"pipelineArr": 2400,
"wonArr": 1735,
"projectedArr": 291,
"paybackDays": 4
}
}Outside benchmark: none measures ICP Organizations per partner build; Other Startups’ results, +14 per build, ■■□□ Early sign, n 96 builds, stand in for one.
Before it · After it. Before it: the weekly rank reads the tools your Customers connect. After it: listed builds go to marketplace-listings, pair Pages to programmatic-seo, and the integration as a product fact to cold-email.
Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days). The weekly rank read 41 Customers’ and 118 Prospects’ stacks; 3 builds went green, 1 listed, 1 is proposed and 1 parked. Read every captured thread at ?threads=; three follow the tables.
| What happened, by option | Decisions | Sent or published | ICP Organizations reached | Won (credited, not all caused by this Play) |
|---|---|---|---|---|
| Build it privately · Park it | 3 · 2 | 3 builds, all private | — | — |
| Propose to the partner | 3 | 3 proposals, 1 yes | — | — |
| List it | 1 | Ledgerly’s store listing + 2 pair Pages | 34 | 4 on Pro (+$1,388 ARR) |
| Move shared Customers onto it · Introduce a Lead | 1 · 2 | 17 emails (2 held back) · 2 Leads | 7 | 1 on Pro (+$347), 2 trials still deciding |
| All · Before (90 days, per 56) | 12 | — | 41 · 6 | $1,735 · $347 |
| At 2 live builds (the Use it when volume) | about 12 in 56 days | — | before-after, not proof | Other Startups’ results: +14 per build, ■■□□ Early sign, n 96 |
| Day | Event | What happened | Your time |
|---|---|---|---|
| 0 | event_128 Campaign.created | the three steps; dry run fails sandbox-connected and domain-authenticated | 25 min |
| 3 | event_170 App.passed | the Ledgerly build passes its sandbox suite, privately | — |
| 15 | event_330 Integration.connected | Maple Court, a paying Customer, connects through the private link: the gate opens | — |
| 18 | event_371 App.published | you approved Ledgerly’s first store listing | 10 min |
| 28 · 56 | event_534 Change.merged · event_908 Readout.created | the build line at 8 Customers: replayed at 1 parked build, read back at +28d at 0 | 5 min |
| 40 | event_690 Campaign.paused | red-build: Porchlight’s sandbox changed an endpoint; green again in 26 h | — |
| 1–56 | event_151 Approval.created, first of 4 | you approved 3 proposals and 1 listing, and signed 1 Agreement | 1.2 h · total 6.5 h measured, 7 h asked |
Leaseline → Ledgerly partner team, Aug 28, 10:05 · 23 property managers on Leaseline keep their books in Ledgerly.¹ We built the integration against your sandbox, and it passes end to end: renewals post as receivables the day they sign.² Here it is, with your half of the listing drafted.³
Ledgerly, Sep 1, 14:30 · “Looks good. List it once a customer is live.” Maple Court connected on Sep 8, and the listing went up Sep 11 with “works with Ledgerly”.
- Leaseline is an official Ledgerly partner.
+ Leaseline works with Ledgerly: renewals post as receivables the day they sign. Ledgerly, 2026-10-14 15:12 · registered Hilltop Rentals (95 units) as a Lead, with Consent on their form. The agent chose Introduce a Lead and sent the Brief to Priya, due 16:00 the next business day. · Priya, 2026-10-15 09:40 · claimed it with
POST /{namespace}/tasks/{task}/claimand booked a call for Oct 17. The Brief is in When it hands to you (task_482).
¹ A count from your own Customers’ connected tools, never a private overlap match. ² A result the suite measured in the partner’s sandbox. ³ Their half of the listing and docs, drafted.
Your settings
---
holdout: 0 # 2 builds are too few to stagger listings; it reads before-after
adoptionHoldout: 0.1
---
Build once 8 Customers or 20 Prospects use a tool. I sign every Agreement.$ npx startup-builder show plays/integration-partners
holdout 0 · adoption holdout 0.1 · build line 8 Customers or 20 Prospects your file plays/integration-partners.mdx:2,3,5 (Ask 2026-08-24; line 5 by change_28, 2026-09-21)
family demand · worker role:partner-manager, owns never · chooses 6 (4 rule, 2 read) · sequences partner-proposal-two-touch, joint-adoption-two-touch, overlap-brief-three-touch, idle-unlist-notice · checks owned-by-a-person, consent-stop + 8 · stops 4 · placements 13 stores and registries, google-search, linkedin, gmail, outlook, yahoo-mail catalog play:integration-partners@1 (Not live yet, gaps #5) Every key from the catalog
Compiled, not written: edit the Play record integration-partners 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.