Skip to content

How SalesBuilder is built

Six layers, from who you reach to what you try next, on three base records: each calls only the layers below it.

Six layers sit on three base records. A layer calls only the layers below it, and Events flow up.

One call per layerMode try
You, to your agent

Show me one call from each layer for our first cold email, as a dry run.

curl -X POST "https://api.sb/leaseline.app/contacts/contact_22/enrich?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"

curl -X POST "https://api.sb/leaseline.app/messages/message_3/schedule?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"at":"2026-11-03T14:00:00Z"}'

curl -X POST "https://api.sb/leaseline.app/contacts/contact_22/enroll?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sequence":"founder-thread"}'

curl -X POST https://api.sb/leaseline.app/tasks/next \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"

curl -X POST "https://api.sb/leaseline.app/play-mixes/compile?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"startup":"leaseline.app"}'
import { $ } from 'startup-builder'
const sb = $.with({ namespace: 'leaseline.app' })

await sb.Contact.enrich({ contact: 'contact_22' }, { mode: 'try' })   // Lists: who it goes to
await sb.Message.schedule({ message: 'message_3', at: '2026-11-03T14:00:00Z' }, { mode: 'try' })   // Sending: from dana@leaseline.app
await sb.Contact.enroll({ contact: 'contact_22', sequence: 'founder-thread' }, { mode: 'try' })   // Sequences: follow-ups that stop when they reply
const { task } = await sb.Task.next()   // Team: your SDR agent’s next Task
return await sb.PlayMix.compile({ startup: 'leaseline.app' }, { mode: 'try' })   // Plays: what to try next
npx startup-builder do Contact.enrich --json '{"contact":"contact_22"}' --try
npx startup-builder do Message.schedule --json '{"message":"message_3","at":"2026-11-03T14:00:00Z"}' --try
npx startup-builder do Contact.enroll --json '{"contact":"contact_22","sequence":"founder-thread"}' --try
npx startup-builder do Task.next
npx startup-builder do PlayMix.compile --json '{"startup":"leaseline.app"}' --try
{
  "name": "do",
  "arguments": {
    "code": "const sb = $.with({ namespace: 'leaseline.app' })\nawait sb.Contact.enrich({ contact: 'contact_22' }, { mode: 'try' })   // Lists: who it goes to\nawait sb.Message.schedule({ message: 'message_3', at: '2026-11-03T14:00:00Z' }, { mode: 'try' })   // Sending: from dana@leaseline.app\nawait sb.Contact.enroll({ contact: 'contact_22', sequence: 'founder-thread' }, { mode: 'try' })   // Sequences: follow-ups that stop when they reply\nconst { task } = await sb.Task.next()   // Team: your SDR agent’s next Task\nreturn await sb.PlayMix.compile({ startup: 'leaseline.app' }, { mode: 'try' })   // Plays: what to try next",
    "mode": "try"
  }
}
Response200application/json
{
  "$id": "https://api.sb/leaseline.app/play-mixes/play-mix_1",
  "$type": "PlayMix",
  "customers": 0,
  "hours": 3,
  "budget": 0,
  "plays": [
    {
      "play": "warm-intros",
      "rank": 1
    },
    {
      "play": "cold-email",
      "rank": 2
    },
    {
      "play": "signup-interviews",
      "rank": 3
    }
  ]
}
sample

A layer calls only the layers below it, and Events flow up: a Sequence never calls a Play, it emits an Event that a Play hears.

Lists

It answersIts filesIts callsNot live yet
Who you sell toaudiences/POST /{namespace}/contacts/{contact}/enrich, POST /{namespace}/contacts/{contact}/merge, POST /{namespace}/visitors/{visitor}/identify, POST /{namespace}/leads/{lead}/qualify, POST /{namespace}/leads/{lead}/assign, POST /{namespace}/contacts/{contact}/suppressOrganization.enrich (Not live yet, gaps #2), Contact.import (Not live yet, gaps #2), Prospect.find (Not live yet, gaps #2)
Pages in this layer
Find your first customers

Content

Sending

Sequences

Team

Pages in this layer
How your agents act

Plays

It answersIts filesIts callsNot live yet
What to try nextplays/POST /{namespace}/play-mixes/compile, POST /{namespace}/experiments/{experiment}/start, POST /{namespace}/experiments/{experiment}/conclude, POST /{namespace}/processes/{process}/pauseCampaign.start (Not live yet, gaps #2), Campaign.pause (Not live yet, gaps #2)

The base

Compiled, not written: edit its page, 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