The audiences/ folder: one file and what it compiles to
What one audiences/ file holds, what each key compiles to, and the checks that read it.
One audiences/ file is one Audience: who is in, who is left out and what fills it. SalesBuilder compiles it, and show traces every key.
---
where: "lead.status = 'nurture' and lead.touchedAt < '-60d'"
or: "deal.status = 'lost' and deal.lostAt < '-90d' and deal.lostReason in ('price', 'missing-capability')"
exclude: [suppressed, customers]
fill: [enrichment]
---
Leads nobody has touched in 60 days, and Deals lost on price or a missing capability. $ npx startup-builder show audiences/lapsed-leads.mdx
where "lead.status = 'nurture' and lead.touchedAt < '-60d'" audiences/lapsed-leads.mdx:2
or "deal.status = 'lost' and deal.lostAt < '-90d' and deal.lostReason in (… audiences/lapsed-leads.mdx:3
exclude [suppressed, customers] audiences/lapsed-leads.mdx:4
fill [enrichment] audiences/lapsed-leads.mdx:5
owner SalesBuilderleaseline.app/
├── plays/
│ ├── cold-email.mdx
│ ├── warm-intros.mdx
│ ├── signup-interviews.mdx
│ ├── lead-reactivation.mdx
│ └── events-and-webinars.mdx
├── audiences/ ← this folder
│ ├── small-property-managers.mdx
│ ├── lapsed-leads.mdx
│ └── renewal-season.mdx
├── sequences/
│ └── cold-email-thread-founder.mdx
├── funnels/
│ └── signup.mdx
├── workflows/
│ └── outbound.mdx
├── agents/
│ ├── sales/prospector.mdx
│ └── marketing/content-lead.mdx
├── systems/
│ └── sequences.mdx
├── domains/
│ └── leaseline.app.mdx
├── offers/
│ ├── pro.mdx
│ └── team.mdx
├── content/
│ └── webinars/60-day-notices.mdx
├── metrics/
│ ├── q4.mdx
│ └── positive-reply-rate.mdx
└── startups.lockThe file
| Key | On the page | Compiles to | Checked by |
|---|---|---|---|
where | Who is in | the List’s query, read by POST /{namespace}/collections/query | S3 |
or | Also in | a second query, joined to the first | S3 |
exclude | Left out | suppressed Contacts and active Customers, always; the key may add, never drop | S3 |
fill | Filled by | the capabilities that enrich it, run after the held-back split, so only treated Contacts are enriched (POST /{namespace}/contacts/{contact}/enrich) | — |
Checks
| Check | It holds | What dev prints |
|---|---|---|
| S3 | Lists leave out suppressed Contacts and read Consent. | audiences/ |
Refusals
| Code | Fix, then the call that clears it |
|---|---|
buyer-stop | Send nothing more, and suppress the Contact on every channel. Then POST /{namespace}/contacts/{contact}/suppress. |
consent-stop | Ask once for a yes to the other Startup. Then POST /{namespace}/messages/{message}/schedule. |
customer-collision | Send it to their CSM instead: the Contact leaves the List. Then POST /{namespace}/leads/{lead}/assign. |
no-consent | Record the Contact’s yes, with where they gave it, before the first send. Then POST /{namespace}/consents. |
suppressed-contact | Leave them out: run the List again, which drops every suppressed Contact. Then POST /{namespace}/collections/query. |
Calls
You, to your agent
Build the small-property-managers List and enrich one Contact on it, as a dry run.
curl -X POST "https://api.sb/leaseline.app/audiences?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"id":"audience_1","type":["noun_1"]}'
curl -X POST "https://api.sb/leaseline.app/contacts/contact_1/enrich?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"import { $ } from 'startup-builder'
await $.Audience.create({ id: 'audience_1', type: ['noun_1'] }, { mode: 'try' }) // audiences/ small-property-managers.mdx
return await $.Contact.enrich({ contact: 'contact_1' }, { mode: 'try' }) // after the held-back split, only treated Contacts
npx startup-builder do Audience.create --json '{"id":"audience_1","type":["noun_1"]}' --try
npx startup-builder do Contact.enrich --json '{"contact":"contact_1"}' --try{
"name": "do",
"arguments": {
"code": "await $.Audience.create({ id: 'audience_1', type: ['noun_1'] }, { mode: 'try' }) // audiences/small-property-managers.mdx\nreturn await $.Contact.enrich({ contact: 'contact_1' }, { mode: 'try' }) // after the held-back split, only treated Contacts",
"mode": "try"
}
}Response200
application/json{
"contact": {
"$id": "https: //api.sb/ leaseline.app/ contacts/ contact_1",
"$type": "Contact",
"email": "dana@leaseline.app",
"phone": "Example phone"
},
"action": {
"$id": "https: //api.sb/ leaseline.app/ actions/ action_1",
"operation": "Contact.enrich",
"status": "done"
}
}
sample| Call | What it does |
|---|---|
POST /{namespace}/contacts/{contact}/enrich | Enrich fills a Contact’s missing fields from outside sources and marks each value with where it came from. Emits Contact.enriched. |
POST /{namespace}/contacts/{contact}/merge | Merge joins a duplicate Contact into another, keeping every link, Message and Event on the one that remains. Emits Contact.merged. |
POST /{namespace}/visitors/{visitor}/identify | Identify joins a Visitor to the Contact it turns out to be, so earlier Visits count toward that person. Emits Visitor.identified. |
POST /{namespace}/organizations/{organization}/merge | Merge joins a duplicate Organization into another, keeping every Contact, Deal and Event on the one that remains. Emits Organization.merged. |
POST /{namespace}/prospects/{prospect}/engage | Engage records a Prospect’s first reply or raised hand and makes it a Lead that keeps its Acquisition. Emits Prospect.engaged. |
POST /{namespace}/leads/{lead}/qualify | Qualify confirms a Lead matches an Offer’s ICP and opens a Deal for it. Emits Lead.qualified. |
POST /{namespace}/leads/{lead}/assign | Assign gives a Lead one owner Worker, by round robin, territory or ICP fit, as the Startup’s routing Policy says. Emits Lead.assigned. |
POST /{namespace}/contacts/{contact}/suppress | Suppress records a Suppression for a Contact’s address, which every Startup honors forever. Emits Contact.suppressed. |
| Not live yet | Use today |
|---|---|
Organization.enrich | Gaps #2. Today: POST /{namespace}/contacts/{contact}/enrich and POST /{namespace}/leads, one Contact at a time. |
Contact.import | Gaps #2. Today: POST /{namespace}/contacts/{contact}/enrich and POST /{namespace}/leads, one Contact at a time. |
Prospect.find | Gaps #2. Today: POST /{namespace}/contacts/{contact}/enrich and POST /{namespace}/leads, one Contact at a time. |
Compiled, not written: edit the records it reads (compile table; cap), 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.