Freemium that converts
Offer a limited free plan and a paid plan that unlocks more usage and route access.
Outcome
Give Quillby a limited Free plan and a $19/month Pro plan. Free users can try the product; Pro raises the limit and unlocks Brand Voice.
Use freemium when a bounded free experience helps customers understand the product before checkout.
Prerequisites
- A frontend business and authenticated CLI
- A reachable HTTPS origin for
POST /v1/generate - A usage meter or resource that can enforce the free boundary
Define the plans and gate
import * as fs from "@farthershore/business";
const requests = fs.requests();
const words = fs.measure("words_generated");
const wordUsage = fs.meter("word_usage", { measures: [words] });
const wordPricing = fs.pricing("word_usage", {
meter: wordUsage,
catalog: [fs.rate.per(1000, fs.money.usd(0.02))],
});
const api = fs.backend("api", {
transport: { mode: "direct" },
default: true,
});
const generate = fs.route("/v1/generate", {
post: { backend: api, costs: [requests.fixed(1)], reports: [wordUsage] },
});
const brandVoice = fs.route("/v1/brand-voice", {
post: { backend: api, costs: [requests.fixed(1)] },
});
fs.meterRoutes("generate-words", generate, {
reports: [wordUsage],
maxOutputUnits: words.atMost(2_000),
});
fs.plan("free", {
kind: fs.plan.kind.free,
grants: [generate],
limits: [requests.perMinute(30)],
});
fs.plan("pro", {
kind: fs.plan.kind.hybrid,
price: fs.money.usd(19).monthly(),
usagePricing: wordPricing.current(),
funding: { buckets: [fs.included(fs.money.usd(10))] },
spendPolicy: {
onExhaustion: fs.exhaustion.overage(wordPricing.current()),
},
grants: [generate, brandVoice],
limits: [requests.perMinute(300)],
});
export default fs.business();
The origin handler for POST /v1/generate must report real usage:
await ctx.report({
meter: "words_generated",
values: { words_generated: generatedWordCount },
});
Direct route grants control access. Free is bounded structurally (30 requests/minute); Pro includes $10 of rated words each month — 500,000 words at $0.02 per thousand — and rates anything past that as overage on the same catalog. Subscribers see the allowance and amount due through the bill preview, never a per-word price they must compute.
Validate and launch
farthershore build --format json
farthershore validate --format json
git add business/ && git commit -m "add Free and Pro" && git push
farthershore backend create quillby \
--name "Quillby API" \
--slug api \
--transport direct \
--origin-url https://api.example.com \
--default \
--idempotency-key <persisted-backend-create-attempt-key> \
--format json
farthershore business publish quillby --dry-run --format json
# After explicit approval of the first draft activation:
farthershore business publish quillby --format json --idempotency-key <persisted-business-publish-attempt-key>
farthershore business status quillby --format json
Poll until status: "ACTIVE" and live: true.
Verify
farthershore backend create quillby \
--env preview \
--name "Quillby API (preview)" \
--slug api \
--transport direct \
--origin-url https://preview-api.example.com \
--default \
--idempotency-key <persisted-backend-create-attempt-key> \
--format json
farthershore persona bootstrap quillby --env preview --plan free --format json --idempotency-key <persisted-persona-bootstrap-attempt-key>
PREVIEW_GATEWAY="https://preview.example.com" # replace with preview hostname
FSK_TEST_KEY="fsk_test_..." # replace with bootstrap output
curl -i "$PREVIEW_GATEWAY/v1/generate" \
-H "x-api-key: $FSK_TEST_KEY"
farthershore usage summary quillby --format json
- The Free persona activates without Stripe checkout.
- Repeated calls increase
words_generated; a burst past 30/minute is deniedrate_limited. - Brand Voice is hidden or upsold for Free.
- Bootstrap a
--plan propersona and confirm Brand Voice is unlocked; the bill preview shows the included allowance draining and overage after it. - The managed usage card matches the usage summary.
Common failures and recovery
| Symptom | Fix |
|---|---|
| Free plan fails validation | A free plan takes no economic controls; bound it with limits. |
| Publish dry run is refused | Follow the stable prerequisite hint, resolve it, and preview again. |
| Paid route leaks to Free | Grant the backend route only to the paid plan; UI hiding is presentation. |
| Usage never increases | Report the exact declared meter key through an attested request-bound channel. |
Fix the contract in business/, build, and publish forward. A new release
reaches new subscriptions; existing subscribers keep their recurring-price pin
and, under wordPricing.current(), follow catalog changes forward.
Next steps
- Metered routes
- Frontend route-aware UI
- Plan changes — including the downgrade direction: a subscriber can move from Pro back to Free from the portal, and that move lands at the end of their current billing period.
Agent prompt
Add Free and Pro plans to Quillby. Free is kind free with an enforced rate
limit. Pro is kind hybrid at $19/month with a $10 included word allowance,
overage on the same catalog, and grants brand-voice. Add managed
usage, plans, and upsell UI; build and validate in preview; then verify Free
denial at limit and Pro access. Show the production publish dry run and ask
before publishing production.