Farther ShoreDocs
Go to Farther Shore
What is FartherShore
Install the CLI
Quickstart
Core concepts
The @Product classThe decoratorsMeters: counted vs. reportedPlans: the billing knobsThe Manifest IRApply and publishEdge enforcementNext
The @Product class
Meters & resources
Features & routes
Capabilities & entitlements
Plans & pricing
The Manifest IR
Bring your own backend
Transport modes
Metering & verification
Runtime tokens
Frontend SDK
Root & data components
Auth & sessions
Entitlement gates
Connect Stripe
Subscriptions & usage
Plan changes & grandfathering
Billing strategies
Apply & deploy
Environments
Migrations
Docs versions & archive
Operate with an agent
Operation classes
MCP server
End-to-end via CLI/MCP
CLI reference
@farthershore/product
@farthershore/backend
@farthershore/farthershore-js
Environment variables
Response & deny codes
Add a metered capability
Gate a feature
Change a price
Prepaid credits
Meter AI tokens
Operate via an agent
Prepare for launch
Status
Docs/Get started/Core concepts

Core concepts

The @Product class, the Manifest IR, and edge enforcement.

PreviousQuickstartNextThe @Product class

A FartherShore product moves through three stages: you author a decorated @Product class, FartherShore compiles it to a deterministic Manifest IR, and the gateway enforces that IR at the edge on every request. This page is the mental model behind those stages and the vocabulary used everywhere else.

@Product class  ──compile──▶  Manifest IR  ──apply──▶  edge gateway
 (you author)                (deterministic)          (enforces live)

The @Product class

A product is one decorated class, export defaulted from product/product.config.ts. The class decorator carries product-level options; each member decorator declares one piece of the product. Cross-references between members are by string key — decorators never hand handles to each other.

import { Product, Requests, Feature, Plan } from "@farthershore/product";

@Product({ name: "croncloud", origin: "https://api.example.com" })
export default class CronCloud {
  @Requests()
  requests!: unknown;

  @Feature("cron-jobs", { routes: { "POST /v1/cron-jobs": {} } })
  cronJobs!: unknown;

  @Plan("starter", {
    name: "Starter",
    price: { amount: 2900, currency: "usd", interval: "month" },
    limits: { requests: { rate: 600, interval: "minute" } },
  })
  starter!: unknown;
}

origin is required: it's the business-logic URL the gateway forwards admitted requests to. name defaults to the class name.

The decorators

These are the building blocks. Each is a member decorator unless noted.

DecoratorDeclares
@Product(options)The class decorator: product identity, origin, billing/policy options.
@Surface(type, …)A surface customers use: frontend, api, docs, widget, webhook, worker, agent, dashboard.
@Meter(key, …)A billable/enforceable usage dimension (tokens, compute, credits).
@Requests(…)The platform-managed request meter; applies requests = 1 to every route.
@Resource(name, …)A counted resource you can cap per plan.
@Capability(key, …)A capability bundle a plan grants to unlock features.
@Feature(key, …)Gateway routes (keyed "METHOD /path"), costs, reports, and actions.
@Plan(key, …)Pricing, limits, grants, and subscriber-change policy.
@Workflow(key, …)A non-HTTP product workflow (scheduled, agent task, …).
@Backend(id, …)A first-class BYO backend; routes bind to it by id.
@Entitlement(key, …)A reusable bundle of capabilities, feature gates, limits, and meters.
@Policy(name, …)A reusable policy layer declared in code.
@Frontend(options)The frontend nav/pages manifest (a stackable class decorator).
@Raw(options)Escape hatch for platform-schema JSON the typed SDK lacks sugar for.

The key words you'll reuse: a product is the sellable thing (one origin, some surfaces); a feature is a named group of gateway routes; a capability is what a plan grants (it includesFeatures, so granting it admits those routes); a meter is a unit of usage; a plan is a priced configuration of grants, limits, and meters; a backend is your upstream (the default is your origin).

Meters: counted vs. reported

@Requests() declares the platform-managed requests meter and needs no backend code — plain request counting is automatic. Variable usage (tokens, compute) is a @Meter that the route declares it reports, and your backend sends with @farthershore/backend:

@Meter("tokens_used", { unit: "token", estimate: 500 })
tokensUsed!: unknown;

@Feature("runs", {
  routes: { "POST /v1/runs": { reports: "tokens_used" } },
})
runs!: unknown;

Per-route metering knobs: cost (fixed gateway-known cost), reports (dynamic meters the backend reports), estimates (pre-request admission estimate), unmetered: true, and inheritDefaultMeters: false. A @Meter's routeDefault applies a fixed cost to every metered route.

Plans: the billing knobs

A @Plan is a configuration of orthogonal knobs. price.amount is integer cents; per-unit overage is in micros. Plans grant capabilities (with optional resource caps via capabilityGrant) and set rate limits per meter:

@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;

A free plan uses price: { free: true }. Credit grants, trials, and spend caps are additional knobs (grants, trialDays, maxMonthlySpendCents). Publishing requires every plan to carry at least one rate-limit rule.

Changing a plan is define (edit the class, publish). Migrating existing subscribers onto a new version is an operate action — a transition over live state — done with farthershore plan migrate, not the manifest. Existing subscribers are grandfathered by default.

The Manifest IR

When you run farthershore build, the class compiles to a Manifest IR envelope (manifest-ir.json) — the backend-owned, deterministic projection the platform actually applies. Member decorators are folded and sorted into stable collections, while routes keep declaration order (the gateway is first-match-wins). The same input always yields the same irHash:

{
  "irVersion": 1,
  "product": {
    "product": { "name": "croncloud", "baseUrl": "https://api.example.com" },
    "plans": [ { "key": "pro" }, { "key": "starter" } ]
  },
  "routes": [
    {
      "feature": "cron-jobs",
      "routes": [
        { "match": { "method": "GET", "path": "/v1/cron-jobs" } },
        { "match": { "method"     
      
    
  
          

You author the ergonomic class; the IR is what gets validated against the live platform contract and applied. The build runs twice and rejects drift, so the IR is reproducible — a pricing change is a reviewable diff.

Apply and publish

Two paths reconcile to the same published state: push accepted product/** changes (the GitHub bot compiles and publishes through Core), or run farthershore product publish <product> to cut a versioned release directly (auto-derived semver, gated on a plan, an origin, and verified Stripe). After either, farthershore product status <product> reports a derived live boolean once the edge is serving the new snapshot.

Edge enforcement

The gateway sits in front of your origin. On each request it:

  1. Authenticates the caller's key and resolves their subscription and plan.
  2. Matches the path against the product's routes (first match wins).
  3. Checks that the plan grants the capability the matched feature requires, and that the caller is under its limits (rate limits, resource caps, credit balance).
  4. Forwards admitted requests to your origin and meters the usage — fixed cost values immediately, reports values once your backend returns them.
  5. Denied requests get a stable error code and never reach your origin.

That's the whole loop from a single class: define it, the gateway enforces it, usage is metered, and Stripe bills it.

Next

  • Quickstart — build and publish this product.
  • What is FartherShore — the bigger picture.
:
"POST"
,
"path"
:
"/v1/cron-jobs"
}
}
]
}
]
,
"capabilities"
:
[
{
"capability"
:
"managed-cron"
,
"includes_features"
:
[
"cron-jobs"
]
}
]
}