Offers and trials: the floor and the trial
Offers and trials: the contribution floor that refuses a thin price, the free trial, and the first Order with the ARR it adds.
An Offer is what you sell at a price. Publishing one checks the price against your target margin, then trials and first Orders run on it.
The record
| Offer | Price | ARR per Customer | Trial | Status |
|---|---|---|---|---|
offer_1 | $29 a month | +$347 ARR | 14 days, no card | published |
offer_2 | $2, | +$2, | none | published |
offer_3 · Starter | $9 a month | +$108 ARR | none | draft: refused below |
[
{
"$id": "https://api.sb/leaseline.app/offers/offer_1" ,
"$type": "Offer",
"price": {
"amount": 29,
"currency": "USD",
"unit": "month"
},
"interval": "month",
"trial": {
"days": 14,
"card": false
},
"status": "published"
},
{
"$id": "https://api.sb/leaseline.app/offers/offer_2" ,
"$type": "Offer",
"price": {
"amount": 2400,
"currency": "USD",
"unit": "year"
},
"interval": "year",
"status": "published"
},
{
"$id": "https://api.sb/leaseline.app/offers/offer_3" ,
"$type": "Offer",
"price": {
"amount": 9,
"currency": "USD",
"unit": "month"
},
"interval": "month",
"status": "draft"
}
]ARR follows what the Customer earned: a $2,400 annual prepay adds $2,393, never 13 times its month, and a one-time Order counts 13 times for four weeks, then ends.
Run it
Publish our $9 Starter plan, start a 14-day trial of the $29 plan for Kestrel Rentals, and place a first Order on the $2,400-a-year plan. Charge no one.
curl -X POST "https://api.sb/leaseline.app/offers/offer_3/publish?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
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_44","days":14}'
curl -X POST "https://api.sb/leaseline.app/orders/place?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"offer":"offer_2"}'
# Not live yet (gaps #4)
curl -X POST https://api.sb/leaseline.app/metrics/query \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"metric":"first-order-arr","shape":"trend","window":"28d"}'import { $ } from 'startup-builder'
await $.Offer.publish({ offer: 'offer_3' }, { mode: 'try' }) // Starter, $9 a month
// 422 · Starter did not publish: at $9 a month the median Customer’s cost of $6.10 leaves 32 %, under your 60 % target margin.
// contribution-floor: Raise the price, or cut the cost each Customer carries, then publish again. · then Offer.update
const trial = await $.Offer.trial({ offer: 'offer_1', customer: 'customer_44', days: 14 }, { mode: 'try' }) // the $29 plan, no card up front
const { order } = await $.Order.place({ offer: 'offer_2' }, { mode: 'try' }) // the $2, 400-a-year plan, paid up front: +$2, 393 ARR, never 13 times it
return await $.Metric.query({ metric: 'first-order-arr', shape: 'trend', window: '28d' }) // the mean ARR a new Customer adds · Not live yet, gaps #4
npx startup-builder do Offer.publish --json '{"offer":"offer_3"}' --try
npx startup-builder do Offer.trial --json '{"offer":"offer_1","customer":"customer_44","days":14}' --try
npx startup-builder do Order.place --json '{"offer":"offer_2"}' --try
npx startup-builder do Metric.query --json '{"metric":"first-order-arr","shape":"trend","window":"28d"}' # Not live yet (gaps #4){
"name": "do",
"arguments": {
"code": "await $.Offer.publish({ offer: 'offer_3' }, { mode: 'try' }) // Starter, $9 a month\n// 422 · Starter did not publish: at $9 a month the median Customer’s cost of $6.10 leaves 32 %, under your 60 % target margin.\n// contribution-floor: Raise the price, or cut the cost each Customer carries, then publish again. · then Offer.update\nconst trial = await $.Offer.trial({ offer: 'offer_1', customer: 'customer_44', days: 14 }, { mode: 'try' }) // the $29 plan, no card up front\nconst { order } = await $.Order.place({ offer: 'offer_2' }, { mode: 'try' }) // the $2,400-a-year plan, paid up front: +$2,393 ARR, never 13 times it\nreturn await $.Metric.query({ metric: 'first-order-arr', shape: 'trend', window: '28d' }) // the mean ARR a new Customer adds · Not live yet, gaps #4",
"mode": "try"
}
}application/json{
"rows": [
{
"week": "2026-10-19",
"value": 347,
"customers": 6
},
{
"week": "2026-10-12",
"value": 347,
"customers": 2
}
]
}
sample · 1 Not live yet+$347 ARR per new Customer · ■■■□ Likely · n 24 Customers in 28 days
$.Metric.query({ metric: · sample
Refuses when
| Check | Fix | Then |
|---|---|---|
contribution-floor fail | Raise the price, or cut the cost each Customer carries, then publish again. | PATCH /{namespace}/offers/{id} |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "Starter did not publish: at $9 a month the median Customer’s cost of $6.10 leaves 32 %, under your 60 % target margin.",
"checks": [
{
"check": "contribution-floor",
"status": "fail",
"observed": "32 % margin at $9, target 60 %",
"doc": "/code/refusals#check-contribution-floor",
"fix": "Raise the price, or cut the cost each Customer carries, then publish again.",
"then": "Offer.update"
}
]
}It says no, says why, and names the fix.
| Check | On | Refuses | Then |
|---|---|---|---|
contribution-floor | POST /{namespace}/offers/{offer}/publish | a price that leaves the median Customer’s cost under your target margin | PATCH /{namespace}/offers/{id} |
Plays that use it
| Play | What it does |
|---|---|
| Closer handoff | Hands a large Deal that stalled twice to a commissioned closer, paid only on collected revenue, and tests it against the Deals your agent keeps. |
| Data residency | Data residency answers where a buyer’s records live, sends the eu Quote with its annex, and holds acceptance until the region reads back. |
| Paid pilot | Runs your product on the buyer’s own data for a fee, against pass marks no higher than they already saw, signed before it starts. |
| Deposits before you build | Sells refundable deposits for what you have not built, for 72 hours after one creator post, and builds only past a pass mark set first. |
| Risk-reversal offers | Takes the risk off the buyer: a written results-or-free bar, a 14-day top-tier trial with no card, a one-press refund and free data moves. |
| Switch kit | Wins your rival’s users with an importer tested on its exports and a dry run they see first, stating no rival fact without a source. |
| Trust pack | Sends the trust page and prefilled security answers with every large Quote, and releases the security report only after a click-through Agreement. |
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.