Skip to content

Lookalikes

Five companies you would love as customers in, 100 more like them out, each with the quote from its own site that shows it fits.

Kind
Workflow
Price
36¢ per 100 companies
Your time
10 minutes
Approval
Runs on its own
Deepens
Outbound
Audience.expand
You, to your agent

Find 100 companies like my five best customers, spend up to $2, and show me the plan first.

curl -X POST "https://api.sb/leaseline.app/audiences/expand?mode=try" \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"goal":"Find 100 companies like my five best customers, spend up to $2, and show me the plan first.","budget":{"amount":2,"currency":"USD"}}'
npx startup-builder do Audience.expand --try --data '{"goal":"Find 100 companies like my five best customers, spend up to $2, and show me the plan first.","budget":{"amount":2,"currency":"USD"}}'
{
  "name": "do",
  "arguments": {
    "mode": "try",
    "code": "return await $.Audience.expand({ goal: 'Find 100 companies like my five best customers, spend up to $2, and show me the plan first.', budget: { amount: 2, currency: 'USD' } })"
  }
}
import { $ } from 'startup-builder'

const result = await $.try.Audience.expand({ goal: 'Find 100 companies like my five best customers, spend up to $2, and show me the plan first.', budget: { amount: 2, currency: 'USD' } })
Response
{
  "mode": "try",
  "stored": false,
  "estimate": "About 36¢ per 100 companies",
  "sample": {
    "company": "Maple Court Homes",
    "similar to": "northgate-rentals.example",
    "fit": "strong",
    "quote": "“Our team manages 180 rental homes across the Twin Cities”"
  }
}
Try mode reads, stores nothing and spends nothingAPI referenceIts Nouns

What you get

  • 100 companies · Five companies you would love as customers in, 100 more like them out, each with the quote from its own site that shows it fits. · Audience
  • audience · The kept companies, saved as an account Audience.
  • companies[].similarTo · The seed each company was found from, so you can see which seed draws the best fits.
  • companies[].fit · strong, possible or unknown, with the quote and its page for a strong fit.
  • bySeed · Strong fits per seed: drop a seed that draws under 10 in 100.

How long: About a minute.

Trigger

It runs when you call it, and each time an order is placed: the new customer becomes a seed.

TypeScript
on.Order.placed((order, $) => $.Audience.expand({ seeds: [order.customer.domain], take: 25 }))

Steps

Your agent does these steps, one row per step; each step’s check is part of its row and its price.

StepVerbReturnsPriceApproval
1. Read your seedsCollection.queryThe five seed companies: your top customers by revenue, or the ones you namedFreeRuns on its own
2. Find companies like each seedOrganization.findOne and a half rows for every company wanted, each tagged with its seedAbout 6¢Runs on its own
3. Ask each site the fit questionOrganization.findfit with a quote code finds verbatim on the page, or unknownAbout 30¢Runs on its own
4. Save the strong fits as an AudienceAudience.createAn account Audience of take strong fitsFreeRuns on its own

Calls: Collection.query, then Organization.find, then Audience.create.

About 36¢ per 100 companies, each time it runs, capped at 75¢.

Example: give it a goal

Find 100 companies like my five best customers, spend up to $2, and show me the plan first.

The plan it shows runs 4 steps, priced before it spends anything; Run it above shows the result it returns.

When something fails

Each failure, and what happens next
WhenThen
Fewer than 3 seeds are givenIt refuses with too-few-seeds and the fix “Name three companies like your best customer”, before it spends.
A seed returns no lookalikesIt is skipped and named in bySeed; the others carry on.
A site cannot be readThe company reads fit: unknown and stays out; the question costs nothing when no page answers.
You call it again the same day with the same seedsIt returns the stored Audience at $0: it is idempotent per seeds and day.

Each step retries three times, then falls through to the next route; one item's failure lands in skipped with its reason, never failing the call. Before a step would cross maxCost it stops and returns what it has, with a resume link. Each step's idempotency key is the call's key plus the step and the item: leaseline-audience-expand-1-<step>-<item>.

Change it

AskCompiles to
Find the operations lead at each, with a checked email.Audience.build
Drop any seed that finds fewer than 10 strong fits.Audience.expand
Sync this list to my Meta ad account as an audience.Integration.sync
TypeScript
import { $, workflow } from 'startup-builder'

export default workflow({
  name: 'audience-expand',
  verb: 'Audience.expand',
  input: { seeds: "'top-customers' | string[]", take: 'integer', question: 'string?', people: 'boolean' },
  maxCost: 0.75,
  execute: async (ctx, { seeds = 'top-customers', take = 100, question, people = false }) => {
    const s = seeds === 'top-customers' ? (await ctx.step('seeds', () => $.Collection.query({ from: 'customer', sort: '-arr', limit: 5 }))).map((c) => c.domain) : seeds
    if (s.length < 3) return ctx.refuse('too-few-seeds', 'Name three companies like your best customer')
    const like = await ctx.step('similar', () => $.Organization.find({ similarTo: s, limit: Math.ceil(take * 1.5), maxCost: 0.1 }))
    const fit = await ctx.step('fit', () => $.Organization.find({ hostnames: like.organizations.map((o) => o.hostname), question, maxCost: 0.35 }))
    const strong = fit.organizations.filter((o) => o.fit === 'strong' && o.evidence.quoteFound).slice(0, take)   // a declared rule
    const audience = await ctx.step('save', () => $.Audience.create({ name: `Like my best · ${ctx.today}`, members: strong }))
    return people ? ctx.step('people', () => $.Audience.build({ from: audience.$id, titles: ['owner', 'head of operations'], size: take })) : audience
  },
})

Extend it with extends: 'Audience.expand' and base(), or compose your own from these steps.

Works with

Plays that order it: Portfolio cross-sell, House ads, Sibling audiences.

It serves Find your first customers, and runs in Hands-on. It deepens Outbound.

For agents

For agents: the guide, its approvals, retries and lookups
FactValue
GuideAudience.expand
Guide approvallimits
GoalFind 100 companies like my five best customers, spend up to $2, and show me the plan first. It may change counts, the order of steps, soft filters and the route a read takes, and it stops early when the goal is met. It never relaxes your words, raises its own budget, answers its own approval, calls a Play or acts outside your standing approval.
Triggeron.Order.placed((order, $) => $.Audience.expand({ seeds: [order.customer.domain], take: 25 }))
RetryThree times with backoff, then the next route; the same idempotency key never charges, sends or posts twice
Idempotency keyleaseline-audience-expand-1
LookupsA find: input asks unless exactly one record fits
Step
1. Read your seeds
2. Find companies like each seed
3. Ask each site the fit question
4. Save the strong fits as an Audience

The amounts live once, on the hub (approvals).

Who does each step, and how it is checked
StepFunctionDoerCheckerAssuranceInputs
1. Read your seedscodeCodeCode checks each result against its declared typemeasured$0.seeds
2. Find companies like each seedcodeCodeCode checks each result against its declared typemeasured$1.seeds
3. Ask each site the fit questioncodeCodeCode finds the quoted evidence on the pagemeasured$2.organizations, $0.question
4. Save the strong fits as an AudiencecodeCodeCode reads the result back from where it landedmeasured$3.strong, $0.take

The doer is never the checker, and the weakest assurance on the path is the whole call’s.

Under the hood

Under the hood: the Systems, Nouns and Events it touches
KindWhat it touches
SystemsAds
NounsCollection, Organization, Audience
EventsCollection.queried, Audience.created

Reference

Inputs, outputs, its Event and its errors
NameTypeDefaultMeaning
seeds'top-customers' | Hostname[]'top-customers'Three to five companies to match: your best customers by revenue, or the ones you would most like to sign.
takeinteger100Strong-fit companies wanted. It stops at take strong fits, not at take rows.
questionstringthe question saved with your ICPAsked of each company’s own site; a fit needs the quote that answers it.
peoplebooleanfalseHand the kept companies to Build a verified list for a checked email at each.
maxCostnumber0.75The most this call spends each time it runs, in USD. It stops before a step would cross it and returns what it has, with a resume link.

Emits Audience.expanded. Refuses with max-cost before it spends, and with the Data calls and Accounts groups (refusals).

Contents