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
- Deepens
- Customer research
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' } })
{
"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"
}
}
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.
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.
| Step | Verb | Returns | Price | Approval |
|---|---|---|---|---|
| 1. Count the companies and their size bands | Organization.count | Matching companies per sentence and how many sit in each headcount or unit band, free | Free | Runs on its own |
| 2. Sample 25 people from each | Prospect.find | A 25-person sample per sentence, names and titles free | Free | Runs on its own |
| 3. Find each sampled work email | ContactPoint.findEmail | An address per person, or a free miss: the found share | About 34¢ | Runs on its own |
| 4. Check each sampled email | ContactPoint.verifyEmail | The valid share of those found; found × valid is the reach yield | About 13¢ | Runs on its own |
| 5. Find companies hiring the role you replace | Organization.find | Each opening with its posted date, in the last 90 days | About 38¢ | Runs on its own |
| 6. Search for dated rule changes | Keyword.search | News results naming a rule or deadline that forces the buyer to act, with its date | About 0.3¢ | Runs on its own |
| 7. Find your warm paths in | Collection.query | Your contacts and connections who work at an account in each sentence now | Free | Runs on its own |
| 8. Measure searches for the problem terms | Keyword.measure | Monthly searches and cost per click for five terms per sentence | About 0.75¢ | Runs on its own |
| 9. Search rivals’ ads | Ad.find | Each rival’s ads with first and last seen; 30 days or more counts as proof | About 5.4¢ | Runs on its own |
| 10. Score each sentence on the rubric | Collection.query | Each reading divided by the best one compared, times its weight, summed out of 100 | Free | Runs on its own |
| 11. Assign you 5 discovery conversations on the winner | Task.assign | Five discovery calls with the winner’s buyers, warm paths first; Build a verified list for the winner waits until all 5 are recorded | Free | Runs 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 runs | What 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. |
| Score | Each 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 list | You 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.
| Reading | Weight | Property managers, 20 to 400 units | Landlords, 1 to 19 units | HOA managers, 5 to 50 staff |
|---|---|---|---|---|
| Pain: events per 100 accounts | 30% | 2.1, so 2.1 ÷ 2.1 = 1.00 | 0.4, so 0.19 | 1.5, so 0.71 |
| ACV ceiling | 25% | $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 × valid | 20% | 0.84 × 0.42 = 0.35, so 1.00 | 0.31 × 0.38 = 0.12, so 0.34 | 0.78 × 0.40 = 0.31, so 0.89 |
| Warm paths | 15% | 9, so 1.00 | 2, so 0.22 | 4, so 0.44 |
| Proof: searches and ads | 10% | 1,900 searches and 6 ads, so 1.00 | 4,400 searches and 2 ads, so 0.93 | 600 searches and 3 ads, so 0.44 |
| Score out of 100 | 100% | 30 + 12.5 + 20 + 15 + 10 = 87.5 | 5.7 + 4 + 6.8 + 3.3 + 9.3 = 29.1 | 21.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
| When | Then |
|---|---|
| A sentence matches under 500 companies | It 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 emails | The 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 points | Both get the 5 discovery conversations, and the pick waits for what the buyers say. |
| You compare the same sentences again the same week | It 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
| Ask | Compiles 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 |
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: 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
| Fact | Value |
|---|---|
| Guide | ICP.compare |
| Guide approval | limits |
| Goal | Compare 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. |
| Retry | Three times with backoff, then the next route; the same idempotency key never charges, sends or posts twice |
| Idempotency key | leaseline-icp-compare-1 |
| Lookups | A 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
| Step | Function | Doer | Checker | Assurance | Inputs |
|---|---|---|---|---|---|
| 1. Count the companies and their size bands | code | Code | Code checks each result against its declared type | measured | $0.icps |
| 2. Sample 25 people from each | code | Code | Code checks each result against its declared type | measured | $0.icps, |
| 3. Find each sampled work email | code | Code | Code checks each result against its declared type | measured | $2.sample |
| 4. Check each sampled email | code | Code | Code checks each result against its declared type | measured | $3.addresses |
| 5. Find companies hiring the role you replace | code | Code | Code checks each result against its declared type | measured | $0.icps, |
| 6. Search for dated rule changes | code | Code | Code checks each result against its declared type | measured | $0.rules |
| 7. Find your warm paths in | code | Code | Code checks each result against its declared type | measured | $0.icps |
| 8. Measure searches for the problem terms | code | Code | Code checks each result against its declared type | measured | $0.problem |
| 9. Search rivals’ ads | code | Code | Code checks each result against its declared type | measured | $0.rivals |
| 10. Score each sentence on the rubric | code | Code | Code checks each result against its declared type | measured | $1..$9, |
| 11. Assign you 5 discovery conversations on the winner | code | Code | Code reads the result back from where it landed | measured | $10.pick, |
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
| Kind | What it touches |
|---|---|
| Systems | None. |
| Nouns | Organization, Prospect, Contact point, Keyword, Collection, Ad, Task |
| Events | Collection.queried, Keyword.measured, Task.assigned |
Reference
Inputs, outputs, its Event and its errors
| Name | Type | Default | Meaning |
|---|---|---|---|
icps | string[] | Required | Two to four customer sentences: who, where, how big. “US property managers with 20 to 400 units” is one. |
price | object | your Offer’s price | What one customer pays a month at each size band, so the ACV ceiling reads in dollars. |
role | string | Required | The job your product does instead of a hire, such as “leasing coordinator”: an open role is a dated pain event. |
rules | string[] | [] | Dated changes that force the buyer’s hand, searched as news: a new reporting rule, a new filing date. |
problem | string[] | Required | Three to five phrases for the problem in the buyer’s words, measured as search terms. |
rivals | Hostname[] | Required | Up to three rivals per sentence, whose long-running ads show someone already pays to reach that buyer. |
sample | integer | 25 | People per sentence whose emails are found and checked to read reachability. |
weights | object | { pain: | The rubric’s weights. Change them and the rubric says so on the scorecard. |
maxCost | number | 2.00 | The 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).