Skip to content

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

OfferPriceARR per CustomerTrialStatus
offer_1$29 a month+$347 ARR14 days, no cardpublished
offer_2$2,400 a year+$2,393 ARRnonepublished
offer_3 · Starter$9 a month+$108 ARRnonedraft: refused below
The Offers on leaseline.app · sample
[
  {
    "$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, trial, first OrderMode try
You, to your agent

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"
  }
}
Response200application/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: 'first-order-arr', shape: 'trend', window: '28d' }) · sample

Refuses when

422RefusedStarter did not publish: at $9 a month the median Customer’s cost of $6.10 leaves 32 %, under your 60 % target margin.
CheckFixThen
contribution-floor failRaise 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.

CheckOnRefusesThen
contribution-floorPOST /{namespace}/offers/{offer}/publisha price that leaves the median Customer’s cost under your target marginPATCH /{namespace}/offers/{id}

Plays that use it

PlayWhat it does
Closer handoffHands 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 residencyData residency answers where a buyer’s records live, sends the eu Quote with its annex, and holds acceptance until the region reads back.
Paid pilotRuns 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 buildSells 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 offersTakes 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 kitWins 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 packSends 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.

Contents