Replies and routing: every reply to an owner
Every reply to every Play goes to one owner by title, with its next step and a deadline.
Replies and routing reads each reply, files it as one of seven kinds and hands it to one owner by title, with a deadline. It costs $0.01 a reply read and routed (sample).
Route every reply to this week’s cold emails to its owner, and answer the ones our SDR agent can. Do not send.
curl -X POST "https://api.sb/leaseline.app/conversations/conversation_31/assign?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"worker":"worker_3"}'
curl -X POST "https://api.sb/leaseline.app/conversations/conversation_34/escalate?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason":"outOfScope"}'
curl -X POST "https://api.sb/leaseline.app/conversations/conversation_36/answer?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"body":"If it does not work in 30 days, we refund you."}'import { $ } from 'startup-builder'
await $.Conversation.assign({ conversation: 'conversation_31', worker: 'worker_3' }, { mode: 'try' }) // Northgate Rentals: “Interested. Can you send times for Tuesday? ” → the AE in 40 s
await $.Conversation.escalate({ conversation: 'conversation_34', reason: 'outOfScope' }, { mode: 'try' }) // “Do you sync with an accounting system? ” → a person
return await $.Conversation.answer({ conversation: 'conversation_36', body: 'If it does not work in 30 days, we refund you.' }, { mode: 'try' }) // Lakeside Rentals: “Is there a guarantee? ”
// 422 · The answer to Lakeside Rentals did not send: it promises money back that no Offer term grants.
// claim: Cut the line, or cite the Offer term that grants it. · then Conversation.answer
npx startup-builder do Conversation.assign --json '{"conversation":"conversation_31","worker":"worker_3"}' --try
npx startup-builder do Conversation.escalate --json '{"conversation":"conversation_34","reason":"outOfScope"}' --try
npx startup-builder do Conversation.answer --json '{"conversation":"conversation_36","body":"If it does not work in 30 days, we refund you."}' --try{
"name": "do",
"arguments": {
"code": "await $.Conversation.assign({ conversation: 'conversation_31', worker: 'worker_3' }, { mode: 'try' }) // Northgate Rentals: “Interested. Can you send times for Tuesday?” → the AE in 40 s\nawait $.Conversation.escalate({ conversation: 'conversation_34', reason: 'outOfScope' }, { mode: 'try' }) // “Do you sync with an accounting system?” → a person\nreturn await $.Conversation.answer({ conversation: 'conversation_36', body: 'If it does not work in 30 days, we refund you.' }, { mode: 'try' }) // Lakeside Rentals: “Is there a guarantee?”\n// 422 · The answer to Lakeside Rentals did not send: it promises money back that no Offer term grants.\n// claim: Cut the line, or cite the Offer term that grants it. · then Conversation.answer",
"mode": "try"
}
}application/json{
"type": "https: //startupbuilder.dev/ errors/ refused",
"title": "Refused",
"status": 422,
"detail": "The answer to Lakeside Rentals did not send: it promises money back that no Offer term grants.",
"checks": [
{
"check": "claim",
"status": "fail",
"observed": "“If it does not work in 30 days, we refund you.”",
"doc": "/code/ refusals#check-claim",
"fix": "Cut the line, or cite the Offer term that grants it.",
"then": "Conversation.answer"
}
]
}
sampleWhat it does
| What you pay for | Price per unit (sample) | The call |
|---|---|---|
| A reply read, filed and routed | $0.01 | POST /{namespace}/conversations/{conversation}/assign |
| An answer drafted and sent | $0.04 | POST /{namespace}/conversations/{conversation}/answer |
| A hand-off to a person | $0 | POST /{namespace}/conversations/{conversation}/escalate |
| When they write back | Fixture reply | What happens | Who answers | By when | Call |
|---|---|---|---|---|---|
| Interested | “Interested. Can you send times for Tuesday?” | books the call; the Handoff Brief goes to the AE with what they said, what it chose and why | AE · Priya Shah (person) | 60 min | POST /{namespace}/meetings/book |
| Send me pricing | “Send me pricing first.” | answers with the list Price from the Pricing Page and one next step | SDR · your agent | 5 min | POST /{namespace}/conversations/{conversation}/answer |
| Not now | “Try us again in January.” | the follow-ups pause; a Trigger waits for the date they named | SDR · your agent | at once | POST /{namespace}/enrollments/{enrollment}/pause |
| Wrong person | “Sam, who runs operations, handles this.” | asks them to bring Sam in; it never writes to an address taken from their text | SDR · your agent | 1 business day | POST /{namespace}/conversations/{conversation}/answer |
| Out of office | an auto-reply until Oct 28 | the next step waits for the return date | nobody | the return date | POST /{namespace}/messages/{message}/schedule |
| Question it can’t answer | “Do you sync with an accounting system?” | goes to a person, with the question in the Handoff Brief | AE · Priya Shah, or you | 60 min | POST /{namespace}/conversations/{conversation}/escalate |
| Stop | “Please stop emailing me.” | nothing else sends, from any Play, on any channel | nobody | at once | POST /{namespace}/contacts/{contact}/unsubscribe |
Every reply is routed first with POST /{namespace}/conversations/{conversation}/assign, 40 s after it lands. The Handoff Brief rides on the AE’s Task.
Run it
| Step | Call | What it does here |
|---|---|---|
| 1 | POST /{namespace}/conversations/{conversation}/assign | Northgate Rentals: “Interested. Can you send times for Tuesday?” → the AE in 40 s |
| 2 | POST /{namespace}/conversations/{conversation}/escalate | “Do you sync with an accounting system?” → a person |
| 3 | POST /{namespace}/conversations/{conversation}/answer | Lakeside Rentals: “Is there a guarantee?” |
| Check | Fix | Then |
|---|---|---|
claim fail | Cut the line, or cite the Offer term that grants it. | POST /{namespace}/conversations/{conversation}/answer |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "The answer to Lakeside Rentals did not send: it promises money back that no Offer term grants.",
"checks": [
{
"check": "claim",
"status": "fail",
"observed": "“If it does not work in 30 days, we refund you.”",
"doc": "/code/refusals#check-claim",
"fix": "Cut the line, or cite the Offer term that grants it.",
"then": "Conversation.answer"
}
]
}It says no, says why, and names the fix.
Set it up
| Check | On the day-0 Startup | Fix | Your time | Wait | Then |
|---|---|---|---|---|---|
inbound-source | fail | forward replies to your inbound address | 5 min | none | POST /{namespace}/integrations/connect |
role-unstaffed | warn | staff the AE, or leave it: Interested replies come to you | 0 min | none | POST /{namespace}/roles/{role}/staff |
approved-pages | pass | your pricing Page is live, so Send me pricing answers from it | 0 min | none | POST /{namespace}/sites/{site}/publish |
The records
{
"conversation": {
"$id": "https://api.sb/leaseline.app/conversations/conversation_31" ,
"$type": "Conversation",
"status": "open",
"respondBy": "2026-10-19T16:41:00Z" ,
"firstAnsweredAt": null
}
}{
"task": {
"$id": "https://api.sb/leaseline.app/tasks/task_118" ,
"$type": "Task",
"assignee": "worker_3",
"due": "2026-10-19T16:41:00Z" ,
"on": "conversation_31",
"brief": {
"who": "Lee Park, Northgate Rentals" ,
"said": "Interested. Can you send times for Tuesday?" ,
"kind": "interested",
"next": "book the call"
}
}
}Calls
| Call | What it does | What else happens | Event |
|---|---|---|---|
POST /{namespace}/conversations/{conversation}/assign | Assign routes a Conversation to the Worker holding the right Role, by topic, Customer tier and language. | — | Conversation.assigned |
POST /{namespace}/conversations/{conversation}/escalate | Escalate hands a Conversation and its summary to the Worker in the Role above, a Human only where one holds it. | A refund over a Limit is an Approval, not an escalation. | Conversation.escalated |
POST /{namespace}/conversations/{conversation}/answer | Answer replies in a Conversation through the Channel it arrived on and closes the reply Task it opened. | Runs the reply through every Guard before it leaves. | Conversation.answered |
POST /{namespace}/meetings/book | Book schedules a Meeting with a Contact at a time both calendars hold free. | — | Meeting.booked |
POST /{namespace}/enrollments/{enrollment}/pause | Pause stops one Contact’s run through a Sequence at its current step, without ending it. | — | Enrollment.paused |
POST /{namespace}/contacts/{contact}/unsubscribe | Unsubscribe stops commercial Messages to a Contact in one click, for one topic or all, honored at once. | Writes a Suppression when no topic is named. | Contact.unsubscribed |
Events
{
"$id": "https://api.sb/leaseline.app/events/event_1" ,
"$type": "Event",
"name": "Conversation.assigned",
"at": "2026-10-22T12:00:00Z" ,
"seq": 1,
"data": {
"conversation": {
"$id": "https://api.sb/leaseline.app/conversations/conversation_1" ,
"$type": "Conversation",
"subject": "Example subject",
"answeredAt": "2026-10-22T12:00:00Z"
}
}
}{
"$id": "https://api.sb/leaseline.app/events/event_1" ,
"$type": "Event",
"name": "Conversation.escalated",
"at": "2026-10-22T12:00:00Z" ,
"seq": 1,
"data": {
"conversation": {
"$id": "https://api.sb/leaseline.app/conversations/conversation_1" ,
"$type": "Conversation",
"subject": "Example subject",
"answeredAt": "2026-10-22T12:00:00Z"
}
}
}{
"$id": "https://api.sb/leaseline.app/events/event_1" ,
"$type": "Event",
"name": "Conversation.answered",
"at": "2026-10-22T12:00:00Z" ,
"seq": 1,
"data": {
"conversation": {
"$id": "https://api.sb/leaseline.app/conversations/conversation_1" ,
"$type": "Conversation",
"subject": "Example subject",
"answeredAt": "2026-10-22T12:00:00Z"
}
}
}{
"$id": "https://api.sb/leaseline.app/events/event_1" ,
"$type": "Event",
"name": "Meeting.booked",
"at": "2026-10-22T12:00:00Z" ,
"seq": 1,
"data": {
"meeting": {
"$id": "https://api.sb/leaseline.app/meetings/meeting_1" ,
"$type": "Meeting",
"at": "2026-10-22T12:00:00Z"
}
}
}{
"$id": "https://api.sb/leaseline.app/events/event_1" ,
"$type": "Event",
"name": "Enrollment.paused",
"at": "2026-10-22T12:00:00Z" ,
"seq": 1,
"data": {
"enrollment": {
"$id": "https://api.sb/leaseline.app/enrollments/enrollment_1" ,
"$type": "Enrollment",
"status": "active",
"step": "Example step"
}
}
}{
"$id": "https://api.sb/leaseline.app/events/event_1" ,
"$type": "Event",
"name": "Contact.unsubscribed",
"at": "2026-10-22T12:00:00Z" ,
"seq": 1,
"data": {
"contact": {
"$id": "https://api.sb/leaseline.app/contacts/contact_1" ,
"$type": "Contact",
"email": "dana@leaseline.app",
"phone": "Example phone"
}
}
}Refuses when
| Check | Refuses | Fix | Then |
|---|---|---|---|
claim | a term with no accepted terms Version, or money back that no Action keeps | Cut the line, or cite the Offer term that grants it. | POST /{namespace}/conversations/{conversation}/answer |
owned-by-a-person Not live yet · gaps #8 · today: The dry run renders from the sample; each check names the call that clears it. | a send to a Contact whose Lead, open Deal or Conversation a person owns | Leave it to the person who owns it: the draft waits in their queue. | POST /{namespace}/tasks/{task}/assign |
suppressed-contact | a send or Sequence step to a Contact on a Suppression, from any Play | Leave them out: run the List again, which drops every suppressed Contact. | POST /{namespace}/collections/query |
| Check | Fix | Then |
|---|---|---|
claim fail | Cut the line, or cite the Offer term that grants it. | POST /{namespace}/conversations/{conversation}/answer |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "The answer to Lakeside Rentals did not send: it promises money back that no Offer term grants.",
"checks": [
{
"check": "claim",
"status": "fail",
"observed": "“If it does not work in 30 days, we refund you.”",
"doc": "/code/refusals#check-claim",
"fix": "Cut the line, or cite the Offer term that grants it.",
"then": "Conversation.answer"
}
]
}It says no, says why, and names the fix.
| Check | Fix | Then |
|---|---|---|
suppressed-contact fail | Leave them out: run the List again, which drops every suppressed Contact. | POST /{namespace}/collections/query |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "Step two to Lakeside Rentals’ owner did not send: they asked us to stop on Oct 12.",
"checks": [
{
"check": "suppressed-contact",
"status": "fail",
"observed": "Suppression suppression_6, unsubscribed 2026-10-12",
"doc": "/code/refusals#check-suppressed-contact",
"fix": "Leave them out: run the List again, which drops every suppressed Contact.",
"then": "Collection.query"
}
]
}It says no, says why, and names the fix.
| Check | Fix | Then |
|---|---|---|
suppressed-contact pass | — | POST /{namespace}/collections/query |
no-consent fail | Record the Contact’s yes, with where they gave it, before the first send. | POST /{namespace}/consents |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "Pinecrest Homes’ second visitor was not enrolled: no Consent on record for sales email.",
"checks": [
{
"check": "suppressed-contact",
"status": "pass"
},
{
"check": "no-consent",
"status": "fail",
"observed": "no Consent for purpose sales-email",
"doc": "/code/refusals#check-no-consent",
"fix": "Record the Contact’s yes, with where they gave it, before the first send.",
"then": "Consent.create"
}
]
}It says no, says why, and names the fix.
Pauses when
| Stop | Pauses when | Fix | Then |
|---|---|---|---|
unanswered-positive | a reply asking to meet that waits 60 minutes for a person | Answer the reply, then approve the resume. | POST /{namespace}/approvals/{approval}/approve |
{
"$id": "https://api.sb/leaseline.app/events/event_951" ,
"$type": "Event",
"name": "Campaign.paused",
"at": "2026-10-19T16:45:00Z" ,
"data": {
"campaign": {
"$id": "https://api.sb/leaseline.app/campaigns/cold-email" ,
"$type": "Campaign"
},
"reason": {
"limit": "unanswered-positive",
"observed": "64m",
"bound": "60m",
"window": "60m"
},
"task": {
"$id": "https://api.sb/leaseline.app/tasks/task_88" ,
"$type": "Task"
}
}
}Plays that use it
| Play | What it does | Its Metric |
|---|---|---|
| Instant response | Answers every form and email within 5 minutes from your own pages, books the call, starts the trial or hands it to the right person. | inbound-meeting-rate |
| Cold email | Writes first to people at companies that showed two fresh public reasons to talk, three short emails at most, and answers every reply by kind. | positive-reply-rate |
| Warm intros | Finds who already knows each company you want, and that person asks them first, from their own account, before any introduction. | intro-acceptance-rate |
| Proof-of-work outreach | Sends each Prospect one finished piece of your product’s work, made from their own public data, in a plain email that asks for nothing. | proof-reply-rate |
| LinkedIn outbound | Picks each day’s People on LinkedIn, drafts the connection note and the Messages, and queues them in your send tray; you press every send. | linkedin-reply-rate |
| Outbound calling | A disclosed voice agent calls back anyone who asks within 60 seconds and answers your number; a person dials every cold call. | callback-meeting-rate |
| Gifting | Sends one gift the Person picks after two relevant touches at a live moment, with you approving each, and counts the Meetings it adds. | gift-meetings-per-100 |
| Lead reactivation | Writes once to each old Lead nobody works, only when something new answers what they asked, from their last contact at the Startup. | reactivated-pipeline |
| Lead nurture | Follows up each unbought Lead with notes keyed to what they did, most in month one, until they buy, reply or go quiet 90 days. | nurture-conversion |
| Account visits | Finds the fitting companies behind visits to your own buying pages and writes to the right person there, from facts, never from the visit. | visit-reply-lift |
| Product signals | Finds teams getting value on a free account and writes to the right person, citing only their own usage, after you approve the first send. | pql-deal-rate |
| Buying committee | Writes to 3 or more roles at each named company, one a week, and hands each engaged company to your AE within a day. | committee-deal-lift |
| Waitlist interviews | Interviews each Waitlist signup with your disclosed agent, admits by fit under a published cap, and sends a result built on their company. | interview-activation-rate |
Cost only: it moves no Metric itself, and what it costs counts against each Play that uses it.
Hub guides it deepens
| On startupbuilder.dev | Id | What it holds |
|---|---|---|
| Conversation | noun: | the record, its fields and every call on it |
| Inbound sales | process: | the Process, its steps and its Roles |
| AE | role: | the Role contract the seat fills |
Example
| Leaseline, when | What happened | Cost (sample) |
|---|---|---|
| 2026-10-19 15:41 | Northgate Rentals writes “Interested. Can you send times for Tuesday?” | — |
| 15:41:40 | filed as Interested and routed to the AE, with the Handoff Brief | $0.01 of Worker cost |
| 16:12 | the AE books Tuesday 14:00 Central, inside the 60-minute window | $0 |
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.