@farthershore/backend
Runtime request-verification and usage metering for builder upstreams.
Runtime request-verification and usage metering for builder upstreams.
@farthershore/backend is the runtime SDK for your upstream. Install one package,
set one runtime credential (FS_RUNTIME_TOKEN), and Farther Shore handles signed
platform-to-upstream request verification plus response-bound usage reporting.
This guide targets the current 0.21 runtime API. Use the
generated export reference for every public
signature and type. It versions independently from @farthershore/business
and @farthershore/farthershore-js.
npm install @farthershore/backend
Mint the token with the CLI — it is returned once. Tokens may be scoped to the business, one environment, or one backend, and may further restrict operations, meters, and route identities:
farthershore backend tokens create croncloud --backend <backendId> --format json --idempotency-key <persisted-backend-tokens-create-attempt-key>
fartherShore.initFromEnv() derives everything — business/backend ids, the
JWKS URL, the metering endpoint, verification config, transport — from
FS_RUNTIME_TOKEN via POST /v1/runtime/bootstrap (cached in memory, refreshed
lazily). The builder configures exactly one thing. See
environment variables.
import { fartherShore } from "@farthershore/backend";
const fs = fartherShore.initFromEnv();
export async function POST(request: Request) {
const url = new URL(request.url);
const body = new Uint8Array(await request.clone().arrayBuffer());
const responseHeaders = new Headers();
// Fail-closed: throws FartherShoreError (→ 401) on any verification failure.
const ctx = await fs.verifyRequest(
{
method: request.method,
path: url.pathname,
query: url.search,
headers: request.headers,
body,
},
{
responseSink: {
canStampHeaders: () => true, // non-streaming: the handler has not returned
stampHeaders: (headers) => {
for (const [name, value] of Object.entries(headers)) {
responseHeaders.set(name, value);
}
},
},
},
);
const result = await runWorkflow(await request.json());
const report = await ctx.report({
meter: "workflow_usage", // matches an fs.meter() key in the business
values: { tokens_used: result.tokensUsed },
});
if (!report.ok) console.error("Usage delivery failed", report.reason);
return Response.json(result, { headers: responseHeaders });
}
The example assumes runWorkflow is your application function and a route bound
to workflow_usage. Map thrown verification errors to their typed HTTP status
in your framework; an uncaught exception alone does not produce a 401. The
response sink is required for Fetch-style in-band reporting. Without it,
verifyRequest() reports over the post-stream channel even before you return a
Response. Streaming adapters must stop permitting header stamps once headers
have actually been sent. Express middleware supplies this adapter for you.
Start with the backend scaffold, which creates app and
fs and installs raw-body capture → fs.middleware() → JSON parsing, in that
order. Preserve that pipeline: verification needs the original request bytes;
installing the verifier alone will reject signed nonempty bodies. The excerpt
below replaces only the scaffold's handler and startup section.
import { requireMember } from "@farthershore/backend";
// app and fs are initialized by the scaffold; body verification runs first.
app.post(
"/v1/cron-jobs",
fs.handler((ctx, req, res) => handler(requireMember(ctx).memberId, req, res)),
);
await fs.ready(app); // register routes first; reconciliation is diagnostic
app.listen(3000);
await fs.start(); // starts the configured embedded tunnel, if any
process.on("SIGTERM", () => void fs.shutdown());
The platform signs each request with Ed25519 and stamps a signed X-Fs-Context
whose hash is bound into that signature. The SDK recomputes the canonical signing
string from the actual request and verifies the signature against a
JWKS-resolved key. Identity comes only from the verified context — the
plaintext X-FS-* headers are untrusted, and fs.middleware() strips every
inbound x-fs-* header after verification so a handler cannot read a spoofable
one. Every failure (missing / malformed / bad-signature / stale / clock-skew /
wrong-route / body-hash-mismatch / replayed-nonce / unknown-kid /
jwks-unavailable) throws and maps to HTTP 401 (413 for oversized bodies).
fs.middleware() is strict by default — it verifies every request
fail-closed and the verified req.fartherShore is a guaranteed, non-optional
presence. Pass { always: false } only for a backend that intentionally consumes
no identity, to defer to the bootstrapped verification.required flag. A direct
call to verifyRequest() always verifies.
fs.handlerfs.handler((ctx, req, res) => …) wraps a route handler so it runs only with a
GUARANTEED verified context: ctx is a non-optional FartherShoreRequestContext
(read ctx.principal / requireMember(ctx) with no optional-chaining), else it
fails closed with a 401. A thrown FartherShoreError /
FartherShorePermissionError (e.g. from requireMember) is mapped to its typed
status.
For products with rbac enabled, the verified context
carries the acting user's resolved permissions so you can do fine-grained,
in-handler checks beyond the route-level enforcement the edge already applied:
import { requirePermission, hasPermission } from "@farthershore/backend";
const ctx = await fs.verifyRequest({ ... }); // throws on any verification failure
requirePermission(ctx, "reports:write"); // throws FartherShorePermissionError (403) if missing
if (hasPermission(ctx, "exports:run")) {
// …
}
ctx.permissions comes only from the signed X-Fs-Context token — the
unsigned x-fs-permissions / x-fs-roles header fallback is gone, and those
headers are stripped before your handler runs. A "*" entry grants everything;
otherwise subject:* and exact subject:verb keys are supported. A missing
permission fails closed. FartherShorePermissionError carries status: 403 and
code permission_denied.
One verb on the verified context: ctx.report({ meter, values, dims?, quote? }).
The meter key is not hardcoded by the SDK — it must match an fs.meter()
declared in the business; values are keyed by
measure key, and dims are optional catalog dimension selectors (string
values). Identity rides the verified context — there is no subscription or
request id argument to forget.
Transport is automatic and invisible:
| Moment | Transport | Network call? |
|---|---|---|
| Before the response is sent | Signed in-band x-fs-metering headers; the platform verifies, settles, and strips them. | No. |
| After the response is on the wire: streams or deferred work for the original served request | The attested post-stream channel, carrying that request's served identity. Billing-only; not reusable for independent cron jobs. | One callback total per served request. |
await ctx.report({
meter: "model_usage",
values: { input_tokens: 1200, output_tokens: 850 },
dims: { model: "acme-4" }, // catalog dimension selectors
});
For a pricing rule declared backendQuoted, an optional
quote: { currency, amountNanos } field carries a proposed, non-negative rate
input that the platform clamps; backends never report money otherwise.
The quote is a per-unit rate, never a total: the platform multiplies it by
the measured quantity. Prefer a decimal integer string for amountNanos to
preserve precision.
One request uses one reporting transport. After a successful in-band report,
a later post-stream report is rejected with ok: false; accumulate the full
measurement before sending, or report the entire final batch after streaming.
Check the returned result. ok: true for an in-band report means headers were
stamped, not that billing settlement has already completed.
Plain request counting (from fs.requests()) is platform-managed and needs no
upstream code.
Only response-bound settlement can affect the request that is currently being served. Post-stream and background reports arrive later and are billing-only; they can affect later requests, not retroactively deny the completed one.
fartherShore instance| Member | Description |
|---|---|
fartherShore.initFromEnv(options?) | Construct an instance; derive everything from FS_RUNTIME_TOKEN. Throws missing_token / invalid_token eagerly. |
fs.middleware(options?) | Express middleware, strict by default: verifies fail-closed, attaches req.fartherShore, and strips inbound x-fs-*. { always: false } defers to the contract flag. |
fs.handler(handler) | Wrap a route handler so it runs only with a GUARANTEED verified context — handler(ctx, req, res) with a non-optional ctx, else 401. |
fs.verifyRequest(input) | Framework-neutral verification primitive ({ method, path, query, headers, body }). |
ctx.report(input) | THE reporting verb, on the verified request context: { meter, values, dims?, quote? }. Transport is chosen automatically (in-band headers or post-stream). |
fs.ready(app?) | Boot-time bootstrap and route reconciliation; reports drift and a ready heartbeat. Fail-open — never blocks boot. |
fs.start() | Start the embedded cloudflared runner for a tunnel backend; no-op otherwise. |
fs.health() | Local runtime health report. |
fs.shutdown() | Graceful: flush metering + send a stopping heartbeat. |
fs.onShutdown(hook) | Register an additional shutdown hook. |
initFromEnv(options) accepts runtimeToken, coreUrl, env, fetchImpl,
verification: { enabled }, metering: { enabled }, tunnel, and instanceId
for tests and advanced opt-outs — but the default DX is everything on, token only.
| Task | Start here | API details |
|---|---|---|
| Add an Express application | Scaffold, then verification | Express adapter |
| Bind an existing service | Named backend, runtime tokens | Runtime contract |
| Record customer data | Verified identity, sharing | Root exports |
| Report usage or stream output | Metering | Root reporting types |
| Receive signed events | Webhook recipe | Webhook receiver |
| Check declared versus implemented routes | Call fs.ready(app) after route registration; inspect drift | Reflection |
| Exercise a handler without credentials | Use the local test workflow below | Testing |
Reflection is diagnostic: it does not install routes or enforce permissions.
Express 5 mounted subrouters can be reported as unreflectable; a partial
reflection result is not proof of complete route coverage. Exercise those paths
explicitly in preview.
The /testing entrypoint supplies createDevRuntime, signed persona clients,
usage/trace sinks, and webhook signers. Use mode: "simulated" for local
verification and permission tests. Passthrough mode deliberately skips checks
and cannot prove authorization. Dev tooling rejects NODE_ENV=production.
Separate three proofs: your handler tests show business behavior, simulated signed requests show verification/permission behavior, and preview traffic shows the accepted route, backend binding, and settlement actually agree. The local dev gateway accepts arbitrary meter keys; a green local report does not prove the deployed measurement schema accepts them.
Test a valid member, a service principal on a member-only handler, a missing permission, a changed signed body, and duplicate usage handling. Always shut down the test runtime. Never deploy fixture keys or dev-mode configuration.
| Export | What it is |
|---|---|
fartherShore, initFromEnv | The conceptual entrypoint and its top-level convenience twin. |
FartherShore, FartherShoreInstance | The runtime class and its augmented type. |
ReportInput, ReportResult, ReportTransport, Measurement, MeasurementValues, MeasurementDimensions, QuoteInput, QuoteProposal, MEASUREMENTS_VERSION | The ctx.report() verb's types. |
computeMeteringHeaders, MeteringError | The framework-neutral signed-header wire recipe (for non-JS backends). |
FartherShoreError, statusForCode | The typed verification error and its HTTP-status mapper. |
verifyRequest, FartherShoreRequestContext, VerifyRequestInput | The standalone verification primitive + types. |
createExpressMiddleware, createExpressHandler, ExpressMiddleware, MiddlewareOptions, VerifiedExpressHandler | Express adapter (strict middleware + guaranteed-context handler). |
JwksClient, NonceCache, BootstrapClient | The lower-level clients initFromEnv composes. |
CloudflaredSupervisor, nodeSpawn, FartherShoreTunnelOptions | The embedded tunnel runner (BYO-backend). |
buildHealthReport, reportHealth, ShutdownManager | Health + shutdown helpers. |
FS_RUNTIME_TOKEN_ENV, RUNTIME_TOKEN_OPERATIONS, RUNTIME_HEADER_NAMES, MAX_BODY_BYTES, RUNTIME_CLOCK_SKEW_SECONDS, RUNTIME_REPLAY_WINDOW_SECONDS, RUNTIME_ERROR_CODES | Shared contract constants (mirrors @farthershore/contracts/runtime). |
hashBody, buildCanonicalSigningString, signCanonicalString, verifyCanonicalSignature, canonicalizeQuery | Signing primitives (one source of truth shared with the platform). |
METERING_PAYLOAD_HEADER, METERING_SIGNATURE_HEADER, METERING_TOKEN_HEADER, DEFAULT_TOKEN_ENV | Response-metering header-contract constants. |
A backend is declared in the product via fs.backend(). Bind routes to it with a
route's backend; a single backend is the default, otherwise mark one
default: true.
import * as fs from "@farthershore/business";
const prodOrigin = fs.backend("prod-origin", {
transport: { mode: "direct" },
verification: { required: true },
default: true,
});
const cronJobs = fs.route("/v1/cron-jobs", {
post: { backend: prodOrigin },
});
fs.plan("pro", {
kind: fs.plan.kind.flat,
price: fs.money.usd(29).monthly(),
grants: [cronJobs],
});
fs.backend() options: name, slug, transport: { mode: "direct" | "tunnel", runner }, verification: { required }, meters (allow-list), and default.
Concrete origins are bound per environment during deployment. A route that
meters a dimension the backend's meters allow-list excludes is rejected at
build time.