Quickstart
Create, define, build, and publish your first product.
Create, define, build, and publish your first product.
This walks the real happy path: install and authenticate, create a product,
author a minimal @Product class, build it, connect billing, add a plan,
publish, and confirm it's live. We'll build CronCloud, a managed cron-jobs
product, the same example used throughout the docs.
Every step is a copy-pasteable command. Pass --format json on any command for
machine-readable output.
npm install -g @farthershore/cli
farthershore auth login --token mk_xxx
farthershore auth whoami
See Install the CLI for tokens, env vars, and CI usage.
Creation provisions a managed GitHub repo containing the editable frontend/
starter and the product/product.config.ts entrypoint, so you need a connected
GitHub account and a repo target. Check connection status first:
farthershore connect github --format json
Then create:
farthershore product create \
--name croncloud \
--display-name "CronCloud" \
--origin https://api.example.com \
--repo-owner your-gh-org \
--repo-name croncloud \
--format json
--name is the product slug (it becomes the subdomain).--origin is the business-logic URL FartherShore calls for customer-facing
actions — the API the gateway forwards to.--repo-owner / --repo-name target the managed GitHub repo to create.The response includes the clone URL. A new product starts as a DRAFT. Clone the repo and install the product workspace:
git clone <clone-url> croncloud
npm install --prefix croncloud/product
Open product/product.config.ts and replace it with the CronCloud manifest. One
class declares the meters, the gateway routes, the capability they require, and
two priced plans that grant it:
import {
Product,
Requests,
Meter,
Resource,
Capability,
Feature,
Plan,
capabilityGrant,
} from "@farthershore/product";
@Product({
name: "croncloud",
origin: "https://api.example.com",
displayName: "CronCloud",
description: "Managed cron jobs",
})
export default class CronCloud {
// Platform-managed request meter — applies `requests = 1` to every route.
@Requests()
requests!: unknown;
// A variable usage meter your backend reports (ms of compute).
@Meter("compute", { display: "Compute", unit: "ms" })
compute!: unknown;
// A counted resource you can cap per plan.
@Resource("cron_jobs", { display: "Cron jobs", countSource: "action_inferred" })
cronJobs!: unknown;
// The capability a plan must grant to reach the feature's routes.
@Capability("managed-cron", {
title: "Managed Cron Jobs",
includesFeatures: ["cron-jobs"],
})
managedCron!: unknown;
// The feature: gateway routes keyed by "METHOD /path".
@Feature("cron-jobs", {
description: "Cron job CRUD",
plans: ["starter", "pro"],
routes: {
"GET /v1/cron-jobs": {},
"POST /v1/cron-jobs": {},
"DELETE /v1/cron-jobs/{id}": {},
},
})
cronJobsFeature!: unknown;
@Plan("starter", {
name: "Starter",
price: { amount: 2900, currency: "usd", interval: "month" },
grants: [capabilityGrant("managed-cron", { limits: { cron_jobs: 10 } })],
limits: { requests: { rate: 600, interval: "minute", enforcement: "enforce" } },
})
starter!: unknown;
@Plan("pro", {
name: "Pro",
price: { amount: 19900, currency: "usd", interval: "month" },
grants: [capabilityGrant("managed-cron", { limits: { cron_jobs: 100 } })],
limits: { requests: { rate: 6000, interval: "minute", enforcement: "enforce" } },
})
pro!: unknown;
}
Cross-references are by string key: the plan grants the "managed-cron"
capability, which includesFeatures: ["cron-jobs"], which owns the routes.
price.amount is integer cents ($29.00 = 2900). See
Core concepts for the full vocabulary.
product/product.config.ts and everything it imports must be deterministic — no
dates, randomness, or network calls. The build runs twice and rejects the push
if the two generated hashes differ.
Compile the class to Manifest IR locally. This is the same compiler the platform runs server-side, so a green build means the manifest is valid:
farthershore build --format json
This emits manifest-ir.json. Fix any reported errors (a missing capability
reference, a malformed route key, a plan without a rate limit) and rebuild until
it's clean.
Publishing requires a verified Stripe connection. The connect flow is browser-only; the CLI reports status so you can poll until it's done:
farthershore connect stripe croncloud --format json
The two plans above are declared in code and apply when the manifest is published. List what's currently on the product at any time:
farthershore plan list croncloud --format json
You can also create a plan directly through the API — for example a free tier to round out the lineup — without editing the manifest. A free plan needs a hard-enforced rate limit:
farthershore plan create croncloud \
--key free \
--name "Free" \
--free \
--rate-limit 60 \
--rate-window minute \
--format json
Take the product live. Publish enforces the same gates as the dashboard: at
least one plan, an origin, and a verified Stripe connection. It auto-derives a
semver bump from the change and cuts a vX.Y.Z release:
farthershore product publish croncloud --format json
Preview the computed version and reasons without cutting a release:
farthershore product publish croncloud --dry-run --format json
If a gate fails, the CLI prints an actionable code — STRIPE_NOT_CONNECTED,
STRIPE_NOT_VERIFIED, or BILLING_TAX_NOT_ENROLLED — with remediation.
Verify the product is actually serving — lifecycle status, the latest release
version, the latest deployment, and a derived live boolean:
farthershore product status croncloud --format json
When status is ACTIVE and live is true, the loop is connected end to
end: the gateway is enforcing your plans in front of https://api.example.com,
admitted requests are metered, and Stripe is billing them.
To send a real authenticated request before you have live customers, mint a
test persona key in a preview environment. It returns an fsk_test_* key
once (store it) you can use to call the gateway and watch usage land:
farthershore env create croncloud --name preview --branch env/preview --format json
farthershore persona bootstrap croncloud --env preview --plan starter --format json
To change the product later, edit product/product.config.ts, rebuild, and
publish again — or just push accepted product/** changes and the GitHub bot
compiles and publishes. The repo is the source of truth either way.
farthershore build --format json
farthershore product publish croncloud --format json
@Product class, the Manifest
IR, and how the gateway enforces it.