Sign-up flows: waitlist to trial
A waitlist, an admission order and a trial, each one call, from the first sign-up to the first paid month.
Sign-up flows take a person from the waitlist to an admitted trial and on to the first paid month. Joining costs $0, and each answer to them $0.04 (sample).
You, to your agent
Admit the next 20 people on the Leaseline waitlist and start their 14-day trials. Do not send.
curl -X POST https://api.sb/leaseline.app/waitlists/waitlist_1/join \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"sam@kestrel.example"}'
curl -X POST "https://api.sb/leaseline.app/waitlists/waitlist_1/admit?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"count":20}'
curl -X POST "https://api.sb/leaseline.app/offers/offer_1/trial?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"customer":"customer_52","days":14}'import { $ } from 'startup-builder'
await $.Waitlist.join({ waitlist: 'waitlist_1', email: 'sam@kestrel.example' }) // a signup from the pricing Page
await $.Waitlist.admit({ waitlist: 'waitlist_1', count: 20 }, { mode: 'try' }) // the next 20, ranked by fit
return await $.Offer.trial({ offer: 'offer_1', customer: 'customer_52', days: 14 }, { mode: 'try' }) // Kestrel Rentals, 40 units
// 422 · Kestrel Rentals’ trial did not start: 14 free days without a card fall below the $29 Offer’s contribution floor.
// contribution-floor: Take a card up front, or shorten the trial to 7 days. · then Offer.trial
npx startup-builder do Waitlist.join --json '{"waitlist":"waitlist_1","email":"sam@kestrel.example"}'
npx startup-builder do Waitlist.admit --json '{"waitlist":"waitlist_1","count":20}' --try
npx startup-builder do Offer.trial --json '{"offer":"offer_1","customer":"customer_52","days":14}' --try{
"name": "do",
"arguments": {
"code": "await $.Waitlist.join({ waitlist: 'waitlist_1', email: 'sam@kestrel.example' }) // a signup from the pricing Page\nawait $.Waitlist.admit({ waitlist: 'waitlist_1', count: 20 }, { mode: 'try' }) // the next 20, ranked by fit\nreturn await $.Offer.trial({ offer: 'offer_1', customer: 'customer_52', days: 14 }, { mode: 'try' }) // Kestrel Rentals, 40 units\n// 422 · Kestrel Rentals’ trial did not start: 14 free days without a card fall below the $29 Offer’s contribution floor.\n// contribution-floor: Take a card up front, or shorten the trial to 7 days. · then Offer.trial",
"mode": "try"
}
}Response422
application/json{
"type": "https: //startupbuilder.dev/ errors/ refused",
"title": "Refused",
"status": 422,
"detail": "Kestrel Rentals’ trial did not start: 14 free days without a card fall below the $29 Offer’s contribution floor.",
"checks": [
{
"check": "contribution-floor",
"status": "fail",
"observed": "14 days, no card",
"doc": "/code/ refusals#check-contribution-floor",
"fix": "Take a card up front, or shorten the trial to 7 days.",
"then": "Offer.trial"
}
]
}
sampleWhat it does
| What you pay for | Price per unit (sample) | The call |
|---|---|---|
| A signup joined to the waitlist | $0 | POST /{namespace}/waitlists/{waitlist}/join |
| A batch admitted | $0 | POST /{namespace}/waitlists/{waitlist}/admit |
| A trial started | $0 | POST /{namespace}/offers/{offer}/trial |
Run it
| Step | Call | What it does here |
|---|---|---|
| 1 | POST /{namespace}/waitlists/{waitlist}/join | a signup from the pricing Page |
| 2 | POST /{namespace}/waitlists/{waitlist}/admit | the next 20, ranked by fit |
| 3 | POST /{namespace}/offers/{offer}/trial | Kestrel Rentals, 40 units |
| Check | Fix | Then |
|---|---|---|
contribution-floor fail | Take a card up front, or shorten the trial to 7 days. | POST /{namespace}/offers/{offer}/trial |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "Kestrel Rentals’ trial did not start: 14 free days without a card fall below the $29 Offer’s contribution floor.",
"checks": [
{
"check": "contribution-floor",
"status": "fail",
"observed": "14 days, no card",
"doc": "/code/refusals#check-contribution-floor",
"fix": "Take a card up front, or shorten the trial to 7 days.",
"then": "Offer.trial"
}
]
}It says no, says why, and names the fix.
Set it up
| Check | On the day-0 Startup | Fix | Your time | Wait | Then |
|---|---|---|---|---|---|
contribution-floor | fail | take a card up front, or shorten the trial to 7 days | 5 min | none | POST /{namespace}/offers/{offer}/trial |
billing-connected | fail | connect Stripe so every trial converts on a live charge | 10 min | none | POST /{namespace}/integrations/connect |
approved-pages | pass | your pricing Page states the trial terms | 0 min | none | POST /{namespace}/sites/{site}/publish |
Reference
The records
{
"waitlist": {
"$id": "https://api.sb/leaseline.app/waitlists/waitlist_1" ,
"$type": "Waitlist",
"invited": 20,
"rank": "fit"
}
}{
"lead": {
"$id": "https://api.sb/leaseline.app/leads/lead_52" ,
"$type": "Lead",
"status": "new",
"interest": "Kestrel Rentals: 40 units" ,
"fit": "partial"
}
}Calls
| Call | What it does | What else happens | Event |
|---|---|---|---|
POST /{namespace}/waitlists/{waitlist}/join | Join adds a Lead to a Waitlist for an Offer that is not open to them. | — | Waitlist.joined |
POST /{namespace}/waitlists/{waitlist}/admit | Admit releases Leads from a Waitlist to the Offer they waited for, in the order the Waitlist ranks them. | — | Waitlist.admitted |
POST /{namespace}/offers/{offer}/trial | Trial starts a Customer on an Offer free for a set number of days, with or without a card, converting or ending by its terms. | With a card on file, converts to a paid Subscription at the end unless canceled first. | Offer.trialed |
Events
{
"$id": "https://api.sb/leaseline.app/events/event_1" ,
"$type": "Event",
"name": "Waitlist.joined",
"at": "2026-10-22T12:00:00Z" ,
"seq": 1,
"data": {
"lead": {
"$id": "https://api.sb/leaseline.app/leads/lead_1" ,
"$type": "Lead",
"status": "new"
},
"waitlist": {
"$id": "https://api.sb/leaseline.app/waitlists/waitlist_1" ,
"$type": "Waitlist",
"invited": 1,
"rank": "signup"
}
}
}{
"$id": "https://api.sb/leaseline.app/events/event_1" ,
"$type": "Event",
"name": "Waitlist.admitted",
"at": "2026-10-22T12:00:00Z" ,
"seq": 1,
"data": {
"leads": [
{
"$id": "https://api.sb/leaseline.app/leads/lead_1" ,
"$type": "Lead",
"status": "new"
}
],
"waitlist": {
"$id": "https://api.sb/leaseline.app/waitlists/waitlist_1" ,
"$type": "Waitlist",
"invited": 1,
"rank": "signup"
}
}
}{
"$id": "https://api.sb/leaseline.app/events/event_1" ,
"$type": "Event",
"name": "Offer.trialed",
"at": "2026-10-22T12:00:00Z" ,
"seq": 1,
"data": {
"subscription": {
"$id": "https://api.sb/leaseline.app/subscriptions/subscription_1" ,
"$type": "Subscription",
"status": "active"
},
"offer": {
"$id": "https://api.sb/leaseline.app/offers/offer_1" ,
"$type": "Offer",
"price": {
"amount": 29,
"currency": "USD"
}
}
}
}Refuses when
| Check | Refuses | Fix | Then |
|---|---|---|---|
contribution-floor | an Offer whose Price leaves less than the target margin after the median Customer’s cost | Raise the Price or lower the cost until the margin clears its target, then publish again. | POST /{namespace}/offers/{offer}/publish |
billing-connected Not live yet · gaps #8 · today: The dry run renders from the sample; each check names the call that clears it. | a Customer-side Play with no live billing records | Connect Stripe so every charge and Meter reading is live. | POST /{namespace}/integrations/connect |
no-consent | a send or Sequence step whose purpose the Contact has given no Consent for | Record the Contact’s yes, with where they gave it, before the first send. | POST /{namespace}/consents |
| Check | Fix | Then |
|---|---|---|
contribution-floor fail | Take a card up front, or shorten the trial to 7 days. | POST /{namespace}/offers/{offer}/trial |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "Kestrel Rentals’ trial did not start: 14 free days without a card fall below the $29 Offer’s contribution floor.",
"checks": [
{
"check": "contribution-floor",
"status": "fail",
"observed": "14 days, no card",
"doc": "/code/refusals#check-contribution-floor",
"fix": "Take a card up front, or shorten the trial to 7 days.",
"then": "Offer.trial"
}
]
}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 |
|---|---|---|---|
kill-switch | you press it, or the injection Stop fires | Review what fired, then turn the Process back on. | POST /{namespace}/processes/{process}/enable |
{
"$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": "kill-switch",
"observed": "pressed by worker_1",
"bound": "off",
"window": "now"
}
}
}Plays that use it
| Play | What it does | Its Metric |
|---|---|---|
| Product-led growth | Gives free Customers enough to reach the result twice, then shows the upgrade inside the task at the Limit, with the real Price. | activation-rate |
| 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 |
| Positioning tournament | Tests three ways of saying what you do, as ads that differ in one phrase, and counts only signups that fit and act. | qualified-signup-rate |
| Agent-to-agent selling | Lets an agent open a working account in one call, locks its sends until a person claims it, then asks that person once to pay. | claimed-paying-rate |
Metric it reaches: Activation rate (activation-rate).
Hub guides it deepens
| On startupbuilder.dev | Id | What it holds |
|---|---|---|
| Lead | noun: | the record, its fields and every call on it |
| Analytics | capability: | the System, its capability and its calls |
Example
| Leaseline, when | What happened | Cost (sample) |
|---|---|---|
| 2026-10-19 | 20 admitted from a waitlist of 64 | $0 |
| 2026-10-19 | Kestrel Rentals starts a 7-day trial with a card on file | $0 |
| day 8 | Kestrel pays $29: +$347 ARR · n 1 · ■□□□ Too early to tell | $0.04 for its one answer |
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.