Skip to content

Pick who to sell to first

Two to four customer sentences in, each scored on pain, deal size, reach, warm paths and proof, and five discovery calls set on the winner.

Kind
Workflow
Price
91¢ per 3 sentences
Your time
20 minutes
Approval
Runs on its own
ICP.compare
You, to your agent

Compare these three customer types and tell me which to start with. Spend up to $2.

curl -X POST "https://api.sb/leaseline.app/icps/compare?mode=try" \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"goal":"Compare these three customer types and tell me which to start with. Spend up to $2.","budget":{"amount":2,"currency":"USD"}}'
npx startup-builder do ICP.compare --try --data '{"goal":"Compare these three customer types and tell me which to start with. Spend up to $2.","budget":{"amount":2,"currency":"USD"}}'
{
  "name": "do",
  "arguments": {
    "mode": "try",
    "code": "return await $.ICP.compare({ goal: 'Compare these three customer types and tell me which to start with. Spend up to $2.', budget: { amount: 2, currency: 'USD' } })"
  }
}
import { $ } from 'startup-builder'

const result = await $.try.ICP.compare({ goal: 'Compare these three customer types and tell me which to start with. Spend up to $2.', budget: { amount: 2, currency: 'USD' } })
Response
{
  "mode": "try",
  "stored": false,
  "estimate": "About 91¢ per 3 customer sentences",
  "sample": {
    "customer": "US property managers, 20 to 400 units",
    "pain per 100": "2.1",
    "ACV ceiling": "$1,788",
    "reach": "0.35",
    "warm paths": "9",
    "score": "87.5",
    "next": "5 discovery calls"
  }
}
Try mode reads, stores nothing and spends nothingAPI referenceIts Nouns

What you get

  • 3 customer sentences · Two to four customer sentences in, each scored on pain, deal size, reach, warm paths and proof, and five discovery calls set on the winner. · ICP
  • rows[].pain · Dated pain events per 100 accounts in 90 days: hiring the role you replace, and rule changes that force a fix.
  • rows[].acv · The yearly price ceiling, from your price at the size bands the sentence covers.
  • rows[].reach · One combined yield: the share of a 25-person sample with an address found, times the share of those that check valid.
  • rows[].access · Accounts where one of your contacts or connections works now: warm intros you could ask for this week.
  • rows[].proof · Monthly searches for the problem terms, and rivals’ ads that have run 30 days or more.
  • rows[].score · The weighted score out of 100, each reading scored against the best sentence compared.
  • pick · The winner, the reading that decided it, and the five discovery conversations assigned to you before any list is built.

How long: About 4 minutes; the sample email finds set the pace.

Trigger

It runs when you call it, or on any Trigger you set.

TypeScript
await $.Workflow.create({ verb: 'ICP.compare', trigger: { schedule: 'weekly' } })

Steps

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

StepVerbReturnsPriceApproval
1. Count the companies and their size bandsOrganization.countMatching companies per sentence and how many sit in each headcount or unit band, freeFreeRuns on its own
2. Sample 25 people from eachProspect.findA 25-person sample per sentence, names and titles freeFreeRuns on its own
3. Find each sampled work emailContactPoint.findEmailAn address per person, or a free miss: the found shareAbout 34¢Runs on its own
4. Check each sampled emailContactPoint.verifyEmailThe valid share of those found; found × valid is the reach yieldAbout 13¢Runs on its own
5. Find companies hiring the role you replaceOrganization.findEach opening with its posted date, in the last 90 daysAbout 38¢Runs on its own
6. Search for dated rule changesKeyword.searchNews results naming a rule or deadline that forces the buyer to act, with its dateAbout 0.3¢Runs on its own
7. Find your warm paths inCollection.queryYour contacts and connections who work at an account in each sentence nowFreeRuns on its own
8. Measure searches for the problem termsKeyword.measureMonthly searches and cost per click for five terms per sentenceAbout 0.75¢Runs on its own
9. Search rivals’ adsAd.findEach rival’s ads with first and last seen; 30 days or more counts as proofAbout 5.4¢Runs on its own
10. Score each sentence on the rubricCollection.queryEach reading divided by the best one compared, times its weight, summed out of 100FreeRuns on its own
11. Assign you 5 discovery conversations on the winnerTask.assignFive discovery calls with the winner’s buyers, warm paths first; Build a verified list for the winner waits until all 5 are recordedFreeRuns on its own

Calls: Organization.count, then Prospect.find, then ContactPoint.findEmail, then ContactPoint.verifyEmail, then Organization.find, then Keyword.search, then Collection.query, then Keyword.measure, then Ad.find, then Collection.query, then Task.assign.

About 91¢ per 3 customer sentences, each time it runs, capped at $2.00.

How it runsWhat it says
Pain urgency, 30%Dated events per 100 accounts in the last 90 days: a posted opening for the role you replace, or a rule change with a date. A segment that has to act this quarter beats one that complains.
ACV ceiling, 25%Your monthly price at the size bands the sentence covers, times 12. It caps what each customer can be worth in year one.
Reachability, 20%One combined yield: found share times valid share on a 25-person sample. Measured find routes hit 31 to 42% each, so the yield is read against your other sentences, never against a fixed floor.
Founder access, 15%Accounts where someone you know works now. Each one is a warm intro you can ask for from your own inbox this week.
Search and ads proof, 10%Half monthly searches for the problem terms, half rivals’ ads running 30 days or more, each against the best sentence.
ScoreEach reading divided by the best sentence’s, times its weight, summed out of 100. The highest score wins, and its weakest reading is named beside it.
Before any listYou hold 5 discovery conversations with the winner’s buyers, warm paths first. Build a verified list for that sentence waits until all 5 are recorded, so the list is built on words you heard.

Leaseline compared three sentences. Property managers win on every reading but deal size, and HOA managers come second on deal size alone.

ReadingWeightProperty managers, 20 to 400 unitsLandlords, 1 to 19 unitsHOA managers, 5 to 50 staff
Pain: events per 100 accounts30%2.1, so 2.1 ÷ 2.1 = 1.000.4, so 0.191.5, so 0.71
ACV ceiling25%$149 × 12 = $1,788, so 1,788 ÷ 3,576 = 0.50$49 × 12 = $588, so 0.16$298 × 12 = $3,576, so 1.00
Reach: found × valid20%0.84 × 0.42 = 0.35, so 1.000.31 × 0.38 = 0.12, so 0.340.78 × 0.40 = 0.31, so 0.89
Warm paths15%9, so 1.002, so 0.224, so 0.44
Proof: searches and ads10%1,900 searches and 6 ads, so 1.004,400 searches and 2 ads, so 0.93600 searches and 3 ads, so 0.44
Score out of 100100%30 + 12.5 + 20 + 15 + 10 = 87.55.7 + 4 + 6.8 + 3.3 + 9.3 = 29.121.3 + 25 + 17.8 + 6.6 + 4.4 = 75.1

Example: give it a goal

Compare these three customer types and tell me which to start with. Spend up to $2.

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

When something fails

Each failure, and what happens next
WhenThen
A sentence matches under 500 companiesIt still reads every column, and the scorecard marks it too-small beside its score; a small market is a finding, not an error.
The sample finds fewer than 15 emailsThe yield prints with its counts, such as “9 found of 25, 4 valid”, and reach reads as unknown rather than low.
Two sentences score within 5 pointsBoth get the 5 discovery conversations, and the pick waits for what the buyers say.
You compare the same sentences again the same weekIt returns the stored scorecard at $0: it is keyed on the sentences, the phrases, the weights and the ISO week.

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-icp-compare-1-<step>-<item>.

Change it

AskCompiles to
Weigh deal size at 40% and pain at 15%.ICP.compare
Build a verified list for the winner once my 5 discovery calls are logged.Audience.build
Save the winner as my customer sentence.ICP.update
TypeScript
import { $, workflow } from 'startup-builder'

export default workflow({
  name: 'icp-compare',
  verb: 'ICP.compare',
  input: { icps: 'string[]', price: 'object', role: 'string', rules: 'string[]', problem: 'string[]', rivals: 'string[]', sample: 'integer', weights: 'object' },
  maxCost: 2,
  execute: async (ctx, { icps, price, role, rules = [], problem, rivals, sample = 25, weights = { pain: 0.3, acv: 0.25, reach: 0.2, access: 0.15, proof: 0.1 } }) => {
    const rows = []
    for (const icp of icps) {
      const bands = await ctx.step(`count:${icp}`, () => $.Organization.count({ where: { description: icp }, by: 'headcount' }))   // free
      const people = await ctx.step(`people:${icp}`, () => $.Prospect.find({ where: { description: icp }, limit: sample }))     // free
      const found = await ctx.step(`emails:${icp}`, () => $.ContactPoint.findEmail({ people: people.prospects, maxCost: 0.15 }))
      const checked = await ctx.step(`check:${icp}`, () => $.ContactPoint.verifyEmail({ emails: found.emails, maxCost: 0.05 }))
      const hiring = await ctx.step(`hiring:${icp}`, () => $.Organization.find({ where: { description: icp, hiringFor: [role], postedWithin: '90d' }, limit: 250, maxCost: 0.15 }))
      const news = await ctx.step(`rules:${icp}`, () => $.Keyword.search({ keywords: rules, venue: 'google-news', since: '90d', maxCost: 0.1 }))
      const paths = await ctx.step(`paths:${icp}`, () => $.Collection.query({ from: 'contact', where: { worksAt: { description: icp } } }))  // your own records
      const demand = await ctx.step(`demand:${icp}`, () => $.Keyword.measure({ keywords: problem, measures: ['searches'], maxCost: 0.01 }))
      const ads = await ctx.step(`ads:${icp}`, () => $.Ad.find({ advertiser: rivals, minDays: 30, maxCost: 0.03 }))
      rows.push({
        icp,
        pain: ((hiring.total + news.dated.length) / bands.count) * 100,
        acv: priceAt(price, bands) * 12,
        reach: (found.hits / sample) * (checked.valid / Math.max(found.hits, 1)),       // one combined yield
        access: paths.accounts,
        proof: { searches: sum(demand, 'searches'), ads: ads.count },
      })
    }
    const scored = scoreAgainstBest(rows, weights)                                       // each reading ÷ the best, × its weight
    const pick = scored.sort((a, b) => b.score - a.score)[0]
    await ctx.step('discovery', () => $.Task.assign({ to: 'founder', kind: 'discovery-call', count: 5, about: pick.icp, gate: 'Audience.build' }))
    return { rows: scored, pick }
  },
})

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

Works with

Plays that order it: Cold email, Positioning tournament.

It serves Find your first customers, and runs in Hands-on, Borrowed. It deepens Customer research.

For agents

For agents: the guide, its approvals, retries and lookups
FactValue
GuideICP.compare
Guide approvallimits
GoalCompare these three customer types and tell me which to start with. Spend up to $2. 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.
RetryThree times with backoff, then the next route; the same idempotency key never charges, sends or posts twice
Idempotency keyleaseline-icp-compare-1
LookupsA find: input asks unless exactly one record fits
Step
1. Count the companies and their size bands
2. Sample 25 people from each
3. Find each sampled work email
4. Check each sampled email
5. Find companies hiring the role you replace
6. Search for dated rule changes
7. Find your warm paths in
8. Measure searches for the problem terms
9. Search rivals’ ads
10. Score each sentence on the rubric
11. Assign you 5 discovery conversations on the winner

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

Who does each step, and how it is checked
StepFunctionDoerCheckerAssuranceInputs
1. Count the companies and their size bandscodeCodeCode checks each result against its declared typemeasured$0.icps
2. Sample 25 people from eachcodeCodeCode checks each result against its declared typemeasured$0.icps, $0.sample
3. Find each sampled work emailcodeCodeCode checks each result against its declared typemeasured$2.sample
4. Check each sampled emailcodeCodeCode checks each result against its declared typemeasured$3.addresses
5. Find companies hiring the role you replacecodeCodeCode checks each result against its declared typemeasured$0.icps, $0.role
6. Search for dated rule changescodeCodeCode checks each result against its declared typemeasured$0.rules
7. Find your warm paths incodeCodeCode checks each result against its declared typemeasured$0.icps
8. Measure searches for the problem termscodeCodeCode checks each result against its declared typemeasured$0.problem
9. Search rivals’ adscodeCodeCode checks each result against its declared typemeasured$0.rivals
10. Score each sentence on the rubriccodeCodeCode checks each result against its declared typemeasured$1..$9, $0.weights
11. Assign you 5 discovery conversations on the winnercodeCodeCode reads the result back from where it landedmeasured$10.pick, $7.paths

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
SystemsNone.
NounsOrganization, Prospect, Contact point, Keyword, Collection, Ad, Task
EventsCollection.queried, Keyword.measured, Task.assigned

Reference

Inputs, outputs, its Event and its errors
NameTypeDefaultMeaning
icpsstring[]RequiredTwo to four customer sentences: who, where, how big. “US property managers with 20 to 400 units” is one.
priceobjectyour Offer’s priceWhat one customer pays a month at each size band, so the ACV ceiling reads in dollars.
rolestringRequiredThe job your product does instead of a hire, such as “leasing coordinator”: an open role is a dated pain event.
rulesstring[][]Dated changes that force the buyer’s hand, searched as news: a new reporting rule, a new filing date.
problemstring[]RequiredThree to five phrases for the problem in the buyer’s words, measured as search terms.
rivalsHostname[]RequiredUp to three rivals per sentence, whose long-running ads show someone already pays to reach that buyer.
sampleinteger25People per sentence whose emails are found and checked to read reachability.
weightsobject{ pain: 0.3, acv: 0.25, reach: 0.2, access: 0.15, proof: 0.1 }The rubric’s weights. Change them and the rubric says so on the scorecard.
maxCostnumber2.00The 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 ICP.compared. Refuses with max-cost before it spends, and with the Data calls and Accounts groups (refusals).

Contents