Farther ShoreDocs
Go to Farther Shore
What is FartherShore
Install the CLI
Quickstart
Core concepts
The @Product class
Meters & resources
@Requests — the platform request meter@Meter — any billable dimension@Resource — counted resourcesDeterminism note
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/Define your product/Meters & resources

Meters & resources

@Requests, @Meter, and @Resource — the billable and enforceable dimensions of a product.

PreviousThe @Product classNextFeatures & routes

Meters are the dimensions you measure and bill on. A product declares them as class members so features can report against them and plans can rate-limit and price them. There are three member decorators: @Requests (the platform request meter), @Meter (any other billable dimension), and @Resource (a counted thing, like cron jobs).

import { Product, Requests, Meter, Resource } from "@farthershore/product";

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

  @Meter("compute", { display: "Compute", unit: "ms" })
  compute!: unknown;

  @Resource("cron_jobs", { display: "Cron jobs", countSource: "action_inferred" })
  cronJobs!: unknown;

  @Plan("starter", {
    name: "Starter",
    limits: { requests: { rate: 600, interval: "minute" } },
  })
  starter!: unknown;
}

@Requests — the platform request meter

@Requests() declares the platform-managed successful-request meter and applies a fixed requests = 1 cost to every metered route. You do not need backend code for plain request counting. It compiles to a meter keyed requests with sane defaults:

@Requests()
requests!: unknown;

emits, in product.metering.meters:

{
  "key": "requests",
  "display": "Requests",
  "unit": "request",
  "estimate": 1,
  "enforcementType": "estimated_then_settled",
  "aggregation": "COUNT"
}

Every metered route then inherits defaults: { requests: 1 }. You can override the display, unit, estimate, window, or enforcementType via options (@Requests({ display: "API calls" })), but the per-route cost is fixed at 1 — @Requests does not accept routeDefault.

@Meter — any billable dimension

@Meter(key, options) declares a billable or enforceable dimension you control — tokens, compute milliseconds, credits, rows processed. The first argument is the meter key (the string other declarations reference); display defaults to a title-cased version of the key.

@Meter("tokens_used", { unit: "token", estimate: 500 })
tokensUsed!: unknown;
OptionTypeMeaning
displaystringHuman label. Defaults to a title-cased key (tokens_used → Tokens Used).
unitstringUnit of measure (token, ms, credit, …).
estimatenumberReusable pre-request estimate used for admission checks when this meter is a dynamic route report.
routeDefaultnumberA fixed cost applied to every metered route unless the route opts out.
aggregation"SUM" | "COUNT" | "MAX" | "UNIQUE_COUNT" | "LATEST"How usage rolls up. Dynamic meters default to SUM.
enforcementType"exact_pre_request" | "estimated_then_settled" | "postpaid" | "strict_concurrency"How the gateway admits requests against the meter.
window"minute" | "hour" | "day" | "month" | "billing_period"Rolling window for windowed meters.

routeDefault — a product-wide fixed cost

A meter's routeDefault adds a reusable fixed cost to every metered route. Route-level cost adds on top of the default:

// routeDefault: 2 is the product-wide default cost for api_credits.
@Meter("api_credits", { unit: "credit", routeDefault: 2 })
credits!: unknown;

@Feature("runs", {
  routes: {
    "POST /v1/runs": { cost: { api_credits: 10 } },  // default 2 + explicit 10
  },
})
runs!: unknown;

compiles the route's metering to defaults: { api_credits: 12, requests: 1 }. See features & routes for per-route cost, reports, unmetered, and inheritDefaultMeters.

estimate — required for dynamic pre-request meters

If a route lists a meter in its reports (the upstream reports actual usage after the request), the gateway needs an estimate to admit the request before usage is known. Provide it on the meter (estimate: 500) or per-route (estimates: { tokens_used: 750 }). A reported meter with no estimate anywhere fails the build:

@Meter("tokens_used", { unit: "token" })   // no estimate
tokens!: unknown;

@Feature("chat", { routes: { "POST /v1/chat": { reports: "tokens_used" } } })
chat!: unknown;
// throws: meter "tokens_used" needs an estimate

Dynamic meters are reported at runtime by your backend with @farthershore/backend. The SDK only declares the dimension and its estimate; the actual value is reported per request.

@Resource — counted resources

@Resource(name, options) declares a counted thing whose quantity a plan can cap (e.g. how many cron jobs a subscriber may create). Resource caps land in a plan's capability_limits, not in rate limits[].

@Resource("cron_jobs", { display: "Cron jobs", countSource: "action_inferred" })
cronJobs!: unknown;
OptionTypeMeaning
displaystringHuman label.
scope"subscription" | "subject"Whether the count is per-subscription or per-subject.
subjectTypestringThe subject type when scope: "subject".
countSource"reported" | "action_inferred"action_inferred derives the count from create/delete actions; reported expects your backend to report it.

With countSource: "action_inferred", the count is maintained from feature actions whose resource effect is create or delete (see the cron-job actions on the features page). A plan then caps it:

@Plan("starter", {
  name: "Starter",
  grants: [capabilityGrant("managed-cron", { limits: { cron_jobs: 10 } })],
  limits: { requests: { rate: 600, interval: "minute" } },
})
starter!: unknown;

This compiles to capability_limits: { cron_jobs: 10 } on the starter plan. See plans & pricing for limits, grants, and overage, and capabilities & entitlements for how cron_jobs ties to the managed-cron capability.

Determinism note

Meters are sorted by key in the emitted IR, so declaration order does not affect the IR hash. Plan dimension records (limits, meter, caps) keep declaration order, so their keys must not be integer-like — an integer-like key like "0" throws at decoration time to prevent silent reordering.