Routes & access groups
Declare each HTTP operation's access, metering, policy, and backend binding.
fs.route(path, operations) declares concrete HTTP operations and returns one
grantable ref for the path.
import * as fs from "@farthershore/business";
const requests = fs.requests();
const tokens = fs.measure("tokens");
const tokenUsage = fs.meter("token_usage", { measures: [tokens] });
const api = fs.backend("api", { meters: [requests, tokenUsage] });
const chat = fs.route("/v1/chat", {
post: {
backend: api,
costs: [requests.fixed(1)],
reports: [tokenUsage],
timeout: "30s",
requireMember: true,
surfaces: [fs.surfaces.api],
},
});
fs.meterRoutes("chat-tokens", chat, { reports: [tokenUsage] });
fs.plan("pro", {
kind: fs.plan.kind.flat,
price: fs.money.usd(49).monthly(),
grants: [chat],
limits: [requests.perMinute(60)],
});
export default fs.business();
Paths and methods
Paths start with / and may use named parameters such as {id} or :id.
Declare the exact methods that exist: get, post, put, patch, delete,
head, or options.
Wildcards do not declare callable routes. Use them only as non-granting
fs.meterRoutes() selectors over separately declared operations.
Calling fs.route() more than once for the same normalized path merges
different methods. Declaring the same method twice with different options is a
conflict.
Authentication and subject
Routes require customer authentication by default. public: true removes that
requirement and cannot be combined with a member/service subject requirement.
requireMember: trueadmits only a verified person or personal key.requireService: trueadmits only an organization-owned service credential.- omit both when either verified subject is valid.
Subject requirements are useful when the backend's data model is inherently per-person or machine-only.
Callability and visibility
surfaces is a callability allowlist. Use typed fs.surfaces.api and
fs.surfaces.ui values. Omitting the field permits all supported authenticated
surfaces.
An empty list is invalid, not deny-all. Surface sets are deduplicated and
canonically ordered. public: true cannot be combined with a surface restriction
because there is no credential to classify; hidden: true is still allowed.
A route that does not admit the API surface is omitted from generated API
discovery. Do not confuse hiding a route with denying an otherwise eligible
caller.
hidden: true removes an otherwise API-callable operation from generated API
discovery and returns a not-found response to wrong-surface callers. It is a
visibility control, not an authorization replacement.
Metering and limits
costsrecords a fixed, gateway-known structural amount (requests.fixed(1)).fs.meterRoutes(key, route, { reports, ... })binds the measurement meters the backend reports on this route, plus admission bounds (maxOutputUnits,caps,chunkPolicy,postStream).onStatusCodesnarrows which response outcomes count.rateLimitandquotacreate route-scoped bounds.
Every bounded dimension must be attached to the operation. For the default requests dimension, combine the bound with a request cost:
const requests = fs.requests();
fs.route("/v1/ping", {
get: {
costs: [requests.fixed(1)],
rateLimit: "10/min",
},
});
Timeout and retry
timeout and idleTimeout accept milliseconds or values such as "500ms",
"30s", or "2m", up to ten minutes. retry: true expands to the platform's
bounded safe default. Use retries only for operations whose upstream behavior
is idempotent, or supply your own idempotency mechanism.
The exact retry: true expansion is two total attempts (one retry), on network
errors and 5xx responses, with { base: 100, max: 1000, jitter: 0.2 } backoff
and budgetRatio: 0.1. These are bounds, not a guarantee that every request is
retried. An explicit retry policy may use one to three total attempts; its
retryOn must contain network and/or 5xx, backoff base must be nonnegative,
maximum must be at least base, and jitter and budget ratio must be between zero
and one.
Duration numbers are whole milliseconds from 1 to 600,000. Strings use ms,
s, or m and must resolve to whole milliseconds: "0.5s" is valid,
"0.5ms" is rejected rather than rounded.
Raw policy and shorthand precedence
The advanced policy object is the base; top-level shorthand options override
it. For example, retry: false removes a retry configured inside policy, and
public: false overrides policy.authMode: "public". Do not specify conflicting
forms casually: the compiler resolves them deterministically, not by object-key
order. Equivalent shorthand and canonical policy normalize to the same IR.
Backend and resource effects
backend binds an operation to a logical fs.backend() ref. The concrete
origin is environment-owned and configured after deployment.
Every business that declares gateway routes must declare at least one logical
backend. With exactly one backend, it is the implicit default and route entries
may omit backend. A backendless business is valid only when it declares no
gateway routes, such as a hosted frontend-only product. This makes an
unroutable route a compile error (BACKEND_REQUIRED_FOR_ROUTE) instead of a
product that appears ready but returns Unknown project at runtime.
creates and deletes associate a successful operation with one counted
resource effect. Use reported counts for batch or asynchronous changes.
Importing OpenAPI
The business program remains authoritative. farthershore import openapi is a
local scaffold tool that generates route declarations for review; it does not
publish an OpenAPI document or create a second source of truth. Inspect and
edit the generated refs, policies, grants, and metering before committing.