Skip to content

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.

audiences/lapsed-leads.mdx
---
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.
show
$ 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    SalesBuilder
leaseline.app
leaseline.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.lock

The file

KeyOn the pageCompiles toChecked by
whereWho is inthe List’s query, read by POST /{namespace}/collections/queryS3
orAlso ina second query, joined to the firstS3
excludeLeft outsuppressed Contacts and active Customers, always; the key may add, never dropS3
fillFilled bythe capabilities that enrich it, run after the held-back split, so only treated Contacts are enriched (POST /{namespace}/contacts/{contact}/enrich)—

Checks

CheckIt holdsWhat dev prints
S3Lists leave out suppressed Contacts and read Consent.audiences/lapsed-leads.mdx:4 S3 exclude: drops suppressed. Suppressed Contacts never enter a List: put it back.

Refusals

CodeFix, then the call that clears it
buyer-stopSend nothing more, and suppress the Contact on every channel. Then POST /{namespace}/contacts/{contact}/suppress.
consent-stopAsk once for a yes to the other Startup. Then POST /{namespace}/messages/{message}/schedule.
customer-collisionSend it to their CSM instead: the Contact leaves the List. Then POST /{namespace}/leads/{lead}/assign.
no-consentRecord the Contact’s yes, with where they gave it, before the first send. Then POST /{namespace}/consents.
suppressed-contactLeave them out: run the List again, which drops every suppressed Contact. Then POST /{namespace}/collections/query.

Calls

audiences/ compiledMode try
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"
  }
}
Response200application/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
CallWhat it does
POST /{namespace}/contacts/{contact}/enrichEnrich 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}/mergeMerge joins a duplicate Contact into another, keeping every link, Message and Event on the one that remains. Emits Contact.merged.
POST /{namespace}/visitors/{visitor}/identifyIdentify 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}/mergeMerge joins a duplicate Organization into another, keeping every Contact, Deal and Event on the one that remains. Emits Organization.merged.
POST /{namespace}/prospects/{prospect}/engageEngage 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}/qualifyQualify confirms a Lead matches an Offer’s ICP and opens a Deal for it. Emits Lead.qualified.
POST /{namespace}/leads/{lead}/assignAssign 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}/suppressSuppress records a Suppression for a Contact’s address, which every Startup honors forever. Emits Contact.suppressed.
Not live yetUse today
Organization.enrichGaps #2. Today: POST /{namespace}/contacts/{contact}/enrich and POST /{namespace}/leads, one Contact at a time.
Contact.importGaps #2. Today: POST /{namespace}/contacts/{contact}/enrich and POST /{namespace}/leads, one Contact at a time.
Prospect.findGaps #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.

Contents