URL: /revenue/signup-flows · Id: `page:/revenue/signup-flows`

# 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).

```text title="Ask"
Admit the next 20 people on the Leaseline waitlist and start their 14-day trials. Do not send.
```

```ts title="SDK"
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
```

## What it does
| What you pay for | Price per unit (sample) | The call |
| --- | --- | --- |
| A signup joined to the waitlist | $0 | <Op verb="Waitlist.join" /> |
| A batch admitted | $0 | <Op verb="Waitlist.admit" /> |
| A trial started | $0 | <Op verb="Offer.trial" /> |

## Run it
| Step | Call | What it does here |
| --- | --- | --- |
| <a id="step-1"></a>1 | <Op verb="Waitlist.join" /> | a signup from the pricing Page |
| <a id="step-2"></a>2 | <Op verb="Waitlist.admit" /> | the next 20, ranked by fit |
| <a id="step-3"></a>3 | <Op verb="Offer.trial" /> | Kestrel Rentals, 40 units |

```json title="422 · sample"
{
  "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"
    }
  ]
}
```

## Set it up
| Check | On the day-0 Startup | Fix | Your time | Wait | Then |
| --- | --- | --- | --- | --- | --- |
| <a id="check-contribution-floor"></a>`contribution-floor` | fail | take a card up front, or shorten the trial to 7 days | 5 min | none | <Op verb="Offer.trial" /> |
| <a id="check-billing-connected"></a>`billing-connected` | fail | connect Stripe so every trial converts on a live charge | 10 min | none | <Op verb="Integration.connect" /> |
| <a id="check-approved-pages"></a>`approved-pages` | pass | your pricing Page states the trial terms | 0 min | none | <Op verb="Site.publish" /> |

<div className="sbO-divider" role="separator" aria-label="Reference" data-divider="reference">Reference</div>

## The records
```json title="The Waitlist · sample"
{
  "waitlist": {
    "$id": "https://api.sb/leaseline.app/waitlists/waitlist_1",
    "$type": "Waitlist",
    "invited": 20,
    "rank": "fit"
  }
}
```

```json title="The Lead the signup wrote · sample"
{
  "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 |
| --- | --- | --- | --- |
| <Op verb="Waitlist.join" /> | Join adds a Lead to a Waitlist for an Offer that is not open to them. | — | [`Waitlist.joined`](#event-waitlist-joined) |
| <Op verb="Waitlist.admit" /> | Admit releases Leads from a Waitlist to the Offer they waited for, in the order the Waitlist ranks them. | — | [`Waitlist.admitted`](#event-waitlist-admitted) |
| <Op verb="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`](#event-offer-trialed) |

## Events
<a id="event-waitlist-joined"></a>

```json title="Waitlist.joined · StartupBuilder-Signature"
{
  "$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"
    }
  }
}
```

<a id="event-waitlist-admitted"></a>

```json title="Waitlist.admitted · StartupBuilder-Signature"
{
  "$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"
    }
  }
}
```

<a id="event-offer-trialed"></a>

```json title="Offer.trialed · StartupBuilder-Signature"
{
  "$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`](/code/refusals#check-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. | <Op verb="Offer.publish" /> |
| [`billing-connected`](/code/refusals#check-billing-connected) <NotLiveYet gap="play-dry-run-codes" /> | a Customer-side Play with no live billing records | Connect Stripe so every charge and Meter reading is live. | <Op verb="Integration.connect" /> |
| [`no-consent`](/code/refusals#check-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. | <Op verb="Consent.create" /> |

```json title="422 · sample"
{
  "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"
    }
  ]
}
```


```json title="422 · sample"
{
  "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"
    }
  ]
}
```

## Pauses when
| Stop | Pauses when | Fix | Then |
| --- | --- | --- | --- |
| [`kill-switch`](/code/refusals#stop-kill-switch) | you press it, or the injection Stop fires | Review what fired, then turn the Process back on. | <Op verb="Process.enable" /> |

```json title="Campaign.paused · kill-switch · Not live yet, gaps #2 · sample"
{
  "$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](/plays/product-led) | 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](/plays/signup-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](/plays/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](/plays/agent-to-agent) | 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](/revenue/activation-rate) (`activation-rate`).

## Hub guides it deepens
| On startupbuilder.dev | Id | What it holds |
| --- | --- | --- |
| [Lead](https://startupbuilder.dev/business/nouns/lead) | `noun:lead` | the record, its fields and every call on it |
| [Analytics](https://startupbuilder.dev/systems/data-and-insights/analytics) | `capability:analytics` | 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 |