Skip to content

Champion moved

Each month, your champions checked for a new job, and each who moved to a company that fits added to your accounts with why now.

Kind
Workflow
Price
42¢ per 200 checked
Your time
8 minutes
Approval
Runs on its own
Deepens
Retention
Contact.follow
You, to your agent

Find where my champions moved this quarter, spend up to $2, and show me the plan first.

curl -X POST "https://api.sb/leaseline.app/contacts/follow?mode=try" \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"goal":"Find where my champions moved this quarter, spend up to $2, and show me the plan first.","budget":{"amount":2,"currency":"USD"}}'
npx startup-builder do Contact.follow --try --data '{"goal":"Find where my champions moved this quarter, spend up to $2, and show me the plan first.","budget":{"amount":2,"currency":"USD"}}'
{
  "name": "do",
  "arguments": {
    "mode": "try",
    "code": "return await $.Contact.follow({ goal: 'Find where my champions moved this quarter, spend up to $2, and show me the plan first.', budget: { amount: 2, currency: 'USD' } })"
  }
}
import { $ } from 'startup-builder'

const result = await $.try.Contact.follow({ goal: 'Find where my champions moved this quarter, spend up to $2, and show me the plan first.', budget: { amount: 2, currency: 'USD' } })
Response
{
  "mode": "try",
  "stored": false,
  "estimate": "About 42¢ per 200 champions checked",
  "sample": {
    "person": "Maya C.",
    "from": "Cedar Hollow Rentals",
    "to": "Lakeshore Living",
    "title": "COO",
    "fit": "strong"
  }
}
Try mode reads, stores nothing and spends nothingAPI referenceIts Nouns

What you get

  • 200 champions checked · Each month, your champions checked for a new job, and each who moved to a company that fits added to your accounts with why now. · Contact
  • checked · Champions read this month.
  • moved[] · Each person who changed jobs: from, to, the new title and the new company’s fit.
  • added[] · New companies added to your accounts, with why now: “your champion joined as COO”.

How long: About 3 minutes.

Trigger

It runs monthly, and on each list refresh row marked moved.

TypeScript
every.month(($) => $.Contact.follow({ contacts: 'champions' }))

Steps

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

StepVerbReturnsPriceApproval
1. List your championsCollection.queryEach champion, with their last known employerFreeRuns on its own
2. Read each champion’s profileContact.enrichCurrent title and employerAbout 40¢Runs on its own
3. Find who movedCollection.queryEach person whose employer changed, by the declared ruleFreeRuns on its own
4. Ask each new employer the fit questionOrganization.findfit with the quote code finds on the pageAbout 1.2¢Runs on its own
5. Read each new employer’s profileOrganization.enrichHeadcount and locationAbout 1.2¢Runs on its own
6. Add each fit to your accountsAudience.updateThe company on the account Audience, with why now, which a Play can act onFreeRuns on its own

Calls: Collection.query, then Contact.enrich, then Collection.query, then Organization.find, then Organization.enrich, then Audience.update.

About 42¢ per 200 champions checked, each time it runs, capped at $1.00.

Example: give it a goal

Find where my champions moved this quarter, spend up to $2, and show me the plan first.

The plan it shows runs 6 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 profile cannot be readThe champion keeps their last state and is read first next month.
The new employer does not fitThe move is recorded on the contact and nothing is added.
You call it twice in one monthThe month’s reading returns: it is idempotent per contact and month.

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-contact-follow-1-<step>-<item>.

Change it

AskCompiles to
Write each one a note to say congratulations and ask for an intro.Prospect.reach
Follow every user, not only champions.Contact.follow
Check every two weeks instead of monthly.Contact.follow
TypeScript
import { $, workflow } from 'startup-builder'

export default workflow({
  name: 'contact-follow',
  verb: 'Contact.follow',
  input: { contacts: "'champions' | string[]", question: 'string?' },
  trigger: { schedule: 'monthly' },
  maxCost: 1,
  execute: async (ctx, { contacts = 'champions', question }) => {
    const list = await ctx.step('list', () => $.Collection.query({ from: 'contact', where: contacts === 'champions' ? "role = 'champion'" : `$id in ${contacts}` }))
    const now = await ctx.step('profiles', () => $.Contact.enrich({ contacts: list.map((c) => c.$id), fields: ['title', 'organization'], maxCost: 0.45 }))
    const moved = now.filter((p) => p.organization.hostname !== p.was.organization.hostname)      // the declared rule
    const fit = await ctx.step('fit', () => $.Organization.find({ hostnames: moved.map((m) => m.organization.hostname), question, maxCost: 0.05 }))
    return ctx.step('add', () => $.Audience.update({ id: 'audiences/audience_accounts', add: fit.organizations.filter((o) => o.fit === 'strong'), whyNow: 'champion joined' }))
  },
})

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

Works with

Plays that order it: Champion follow, Signal expansion.

It serves Keep and grow customers, and runs in Keep and grow. It deepens Retention.

For agents

For agents: the guide, its approvals, retries and lookups
FactValue
GuideContact.follow
Guide approvallimits
GoalFind where my champions moved this quarter, 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.
Triggerevery.month(($) => $.Contact.follow({ contacts: 'champions' }))
RetryThree times with backoff, then the next route; the same idempotency key never charges, sends or posts twice
Idempotency keyleaseline-contact-follow-1
LookupsA find: input asks unless exactly one record fits
Step
1. List your champions
2. Read each champion’s profile
3. Find who moved
4. Ask each new employer the fit question
5. Read each new employer’s profile
6. Add each fit to your accounts

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

Who does each step, and how it is checked
StepFunctionDoerCheckerAssuranceInputs
1. List your championscodeCodeCode checks each result against its declared typemeasured$0.contacts
2. Read each champion’s profilecodeCodeCode checks each result against its declared typemeasured$1.contacts
3. Find who movedcodeCodeCode checks each result against its declared typemeasured$1, $2
4. Ask each new employer the fit questioncodeCodeCode finds the quoted evidence on the pagemeasured$3.moved, $0.question
5. Read each new employer’s profilecodeCodeCode checks each result against its declared typemeasured$3.moved
6. Add each fit to your accountscodeCodeCode reads the result back from where it landedmeasured$4.strong

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
SystemsCRM
NounsCollection, Contact, Organization, Audience
EventsCollection.queried, Contact.enriched, Audience.updated

Reference

Inputs, outputs, its Event and its errors
NameTypeDefaultMeaning
contacts'champions' | Contact[]'champions'The people to follow: every contact marked champion, or a list.
questionstringthe question saved with your ICPAsked of each new employer’s site; a fit needs the quote.
everystring'30d'How often they are read.
maxCostnumber1.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 Contact.followed. Refuses with max-cost before it spends, and with the Data calls and Accounts groups (refusals).

Contents