Farther ShoreDocs
Go to Farther Shore
What is FartherShore
Install the CLI
Quickstart
Core concepts
The @Product class
Meters & resources
Features & routes
Capabilities & entitlements
Plans & pricing
The Manifest IR
Bring your own backend
Transport modes
Metering & verification
Verify every request (fail-closed)Report usage on the response (no network call)Async / background usageHealth & shutdownPairing meters with the contract
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/Connect your backend/Metering & verification

Metering & verification

Verify gateway requests and report usage from your backend with @farthershore/backend.

PreviousTransport modesNextRuntime tokens

@farthershore/backend is the SDK your origin imports. It does two jobs: verify that an inbound request really came from the Farther Shore gateway (fail-closed Ed25519), and report how much each request consumed so the platform can meter and bill it. You configure exactly one thing — the FS_RUNTIME_TOKEN — and everything else (product and backend ids, the JWKS URL, the metering endpoint, verification config, transport) is fetched from bootstrap and cached in memory.

npm install @farthershore/backend
import { fartherShore } from "@farthershore/backend";

// Derives everything from FS_RUNTIME_TOKEN via POST /v1/runtime/bootstrap.
const fs = fartherShore.initFromEnv();

Verify every request (fail-closed)

The plaintext X-FS-* headers on a forwarded request are untrusted. Identity comes only from a gateway signature whose claims match the actual request. The SDK recomputes the canonical signing string from the real method, path, query, headers, and body, then verifies the gateway's Ed25519 signature against a JWKS-resolved public key.

Express

fs.middleware() runs the verification and attaches req.fartherShore. Any failure responds 401 (or 413 for an oversized body) — there is no fail-open branch.

import { fartherShore } from "@farthershore/backend";

const fs = fartherShore.initFromEnv();

app.use(fs.middleware()); // fail-closed verify → req.fartherShore

app.post("/v1/cron-jobs", async (req, res) => {
  const job = await createCronJob(req.body);
  res.json(job);
});

app.listen(3000);
process.on("SIGTERM", () => void fs.shutdown());

Any framework

fs.verifyRequest({ method, path, query, headers, body }) is the framework-neutral primitive. It throws a typed FartherShoreError on any failure; otherwise it returns the verified context.

export async function POST(request: Request) {
  const url = new URL(request.url);
  const body = new Uint8Array(await request.clone().arrayBuffer());

  await fs.verifyRequest({
    method: request.method,
    path: url.pathname,
    query: url.search,
    headers: request.headers,
    body,
  });

  // ... handle the verified request ...
}

Every failure mode — missing, malformed, bad-signature, stale, clock-skew, wrong-route, body-hash-mismatch, replayed-nonce, unknown-kid, jwks-unavailable — returns a typed FartherShoreError mapped to HTTP 401 (413 for oversized bodies). Verification is required only when your @Backend sets verification: { required: true }; until then the gateway still forwards, but turning it on is the secure default once your token is provisioned.

Report usage on the response (no network call)

When you know usage by the time you return the response — tokens consumed, credits spent — sign it into the response with withUsage(). This makes no backend→core network call. It signs the usage into internal x-fs-metering response headers with your FS_RUNTIME_TOKEN; the gateway verifies, settles, and strips those headers before the subscriber receives the response.

import { fartherShore, withUsage } 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());

  await fs.verifyRequest({
    method: request.method,
    path: url.pathname,
    query: url.search,
    headers: request.headers,
    body,
  });

  const result = await runWorkflow(await request.json());

  return withUsage(
    request,
    Response.json(result),
    { tokens_used: result.tokensUsed }, // raw usage, keyed by meter
    {
      measureContext: { model: result.model },          // free-form context
      creditUnitsConsumed: { credits: result.creditsUsed }, // credit-wallet map
    },
  );
}

The meter keys (tokens_used above) are not hardcoded by the SDK — each must match a dynamic meter declared in your product contract via @Meter(...) and listed in a route's reports. Keys must be lowercase alphanumeric with underscores (^[a-z0-9_]{1,64}$) and values non-negative finite numbers, validated locally before signing — a bad key or value throws MeteringError. Platform-managed defaults like request counts need no upstream code at all.

createUsage() for incremental reporting

When usage accrues across a handler, build it up with createUsage() and wrap the response at the end. withUsage() is just sugar over this.

import { createUsage } from "@farthershore/backend";

export async function POST(request: Request) {
  const usage = createUsage(request);

  const step1 = await embed(/* ... */);
  usage.report("tokens_used", step1.tokens);

  const step2 = await generate(/* ... */);
  usage.report("tokens_used", step2.tokens);

  return usage.wrap(Response.json({ ok: true }), {
    measureContext: { model: step2.model },
  });
}

Async / background usage

fs.meter(meter, qty, { requestId, routeId }) is for usage that is not tied to a gateway response — background jobs, deferred tallies. Unlike withUsage(), this DOES make a network call: it enqueues an idempotent event and POSTs it to /v1/metering/events with a reusable bearer credential. Delivery is at-least-once and the event_id keeps core's ingest safe; values are tallied and billed post-cycle, not enforced in real time.

// Background reconciliation, hours after the original request.
await fs.meter("tokens_used", 1280, {
  requestId: job.requestId,
  routeId: "cron-job.run",
});

Reach for fs.meter() only when usage is genuinely decoupled from a response. For request-bound usage, prefer withUsage() / createUsage() — they are cheaper (no network call) and settle in the same request the subscriber made.

Health & shutdown

The instance bootstraps lazily on first use. On shutdown it flushes any buffered metering and sends a stopping heartbeat.

fs.health();          // { runtimeToken, bootstrap, tunnel, verification, metering }
await fs.shutdown();  // flush metering + stopping heartbeat (call on SIGTERM)

Pairing meters with the contract

Response-bound reports only settle for meters your product actually declares. Declare the meter and bind it as a route report in the same @Product class:

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

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

  @Feature("runs", {
    plans: ["starter"],
    routes: {
      // The upstream may report `tokens_used` on this route.
      "POST /v1/runs": { reports: "tokens_used" },
    },
  })
  runs!: unknown;

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

See usage metering for meter shapes, runtime tokens for provisioning the token this SDK reads, and the @farthershore/backend reference for the full export surface.