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
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
1. Create the product2. Author the contract as code3. Seed a plan via the CLI (DRAFT only)4. Connect billing5. Publish6. Verify it is liveThe same run over MCPWire up a BYO backend (optional)
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/Automate with agents/End-to-end via CLI/MCP

End-to-end via CLI/MCP

A complete agent-run lifecycle — create, define, add a plan, and publish CronCloud — entirely from the CLI with a maker token, no browser.

PreviousMCP serverNextCLI reference

This is the whole job, start to finish, the way an agent runs it: create a product, author its contract as a decorated @Product class, add a plan, and publish — using only the farthershore CLI and a maker token. The single browser-only step (Stripe Connect) is a status poll, not a blocker the agent performs.

We use CronCloud (managed cron jobs) as the running product.

export FARTHERSHORE_TOKEN=mk_xxx
farthershore auth whoami --format json   # confirm org + scopes before starting

1. Create the product

product create provisions a managed GitHub repo and the editable frontend/ starter, then returns clone-URL + bootstrap instructions. It is a dual operation and CLI-only (it needs a workstation to scaffold the repo).

farthershore product create \
  --name croncloud \
  --display-name "CronCloud" \
  --description "Managed cron jobs" \
  --origin https://api.example.com \
  --repo-owner acme --repo-name croncloud \
  --format json

--origin is the builder-owned URL Farther Shore calls for customer-facing actions. --repo-owner must be a connected GitHub account. The product starts as a DRAFT:

{
  "schema_version": 1,
  "ok": true,
  "op": "product.create",
  "data": { "result": { "id": "prod_…", "name": "croncloud", "status": "DRAFT", "repoUrl": "https://github.com/acme/croncloud" } }
}

No GitHub connection yet? farthershore connect github --format json reports status the agent can poll until the user finishes the browser OAuth. Connect flows are intentionally browser-only.

2. Author the contract as code

Clone the repo and edit product/product.config.ts. The product's plans, routes, meters, and capabilities are the contract — authored as one decorated @Product class (the only authoring surface; there is no YAML). This is the canonical CronCloud shape:

import {
  Capability,
  Feature,
  Meter,
  Plan,
  Product,
  Requests,
  Resource,
  capabilityGrant,
} from "@farthershore/product";

@Product({
  name: "croncloud",
  origin: "https://api.example.com",
  displayName: "CronCloud",
  description: "Managed cron jobs",
  billOn4xx: false,
})
class CronCloud {
  @Requests()
  requests!: unknown;

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

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

  @Capability("managed-cron", {
    title: "Managed Cron Jobs",
    includesFeatures: ["cron-jobs"],
  })
  managedCron!: unknown;

  @Feature("cron-jobs", {
    description: "Cron job CRUD",
    plans: ["starter", "pro"],
    actions: [
      {
        id: "cron-job.create",
        kind: "mutation",
        : ,
        : { : , :  },
      },
      {
        : ,
        : ,
        : ,
        : { : , : , :  },
        : { : , :  },
        : ,
      },
    ],
    : {
      : {},
      : { :  },
      : { :  },
    },
  })
  cronJobsFeature!: ;

  (, {
    : ,
    : { : , : , :  },
    : [(, { : { :  } })],
    : { : { : , : , :  } },
  })
  starter!: ;

  (, {
    : ,
    : { : , : , :  },
    : [(, { : { :  } })],
    : { : { : , : , :  } },
  })
  pro!: ;
}
 ;

Then validate locally. farthershore build executes the decorated class and emits a Manifest IR envelope (manifest-ir.json) — it runs the same manifest gates the GitHub bot enforces (e.g. every plan needs at least one rate-limit rule), so a green local build means the push will compile and apply. The remaining publish gates (a verified Stripe connection, an origin) are checked server-side at publish time.

cd product && npm install        # once
farthershore build --format json # from the repo root

Because both plans above are defined in the manifest, the change goes live when you git push — the GitHub bot compiles and applies it. An out-of-band plan create against this published product would return MANAGED_BY_CODE (exit 4); see operation classes.

3. Seed a plan via the CLI (DRAFT only)

During the DRAFT window — before the first publish — the API still accepts contractual writes so an agent can seed the repo without a push. This is the one time plan create works directly:

farthershore plan create croncloud \
  --key starter --name "Starter" --recurring-fee-cents 2900 --format json

farthershore plan create croncloud \
  --key pro --name "Pro" --recurring-fee-cents 19900 --format json

farthershore plan list croncloud --format json

After publish, the manifest is canonical — edit @Plan and push instead.

4. Connect billing

Publishing requires a verified Stripe connection. The flow is browser-only; the CLI reports status so the agent can poll and hand the URL to the user:

farthershore connect stripe croncloud --format json
# poll until: { "data": { "status": "verified" } }

5. Publish

product publish is a dual operation — an agent may publish its own product. It enforces the same gates as the dashboard: at least one plan, an origin, and a verified Stripe connection.

farthershore product publish croncloud --format json
{ "schema_version": 1, "ok": true, "op": "product.publish", "data": { "status": "ACTIVE" } }

If a gate fails, the envelope is ok: false with the actionable code — MISSING_ORIGIN (a 400, exit 2), or one of the billing gates STRIPE_NOT_CONNECTED / STRIPE_NOT_VERIFIED / BILLING_TAX_NOT_ENROLLED (a 402 the agent surfaces to the user, since connecting Stripe is browser-only). Branch on the exit code and the code, not on the message text.

6. Verify it is live

product status is an operate read — it reflects the deployed runtime state, not just what was applied:

farthershore product status croncloud --format json
farthershore usage summary croncloud --format json

To exercise the live gateway end-to-end, bootstrap a test persona (test-strategy environments only) and call a route with the minted key:

farthershore persona bootstrap croncloud --env preview --plan pro --format json
# → returns an fsk_test_* key; call POST https://croncloud.…/v1/cron-jobs with it

The same run over MCP

A hosted agent without a shell drives the operate/dual steps as fs_* tools — fs_product_publish, fs_product_status, fs_usage_read, fs_persona_bootstrap, fs_runtime_token_mint. The CLI-only steps (create, build, and the contract edits) run in the agent's workstation. See the MCP page for the full tool/command map.

Wire up a BYO backend (optional)

If CronCloud's logic runs on your own server, mint a runtime token and verify requests with @farthershore/backend:

farthershore backend create croncloud \
  --name "Production API" --origin-url https://api.example.com --format json
farthershore backend tokens create croncloud --backend <backendId> --format json
# → returns fsrt_… ONCE; deploy it as FS_RUNTIME_TOKEN
import { fartherShore, withUsage } from "@farthershore/backend";

const fs = fartherShore.initFromEnv();

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

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

  const result = await createCronJob(await request.json());
  return withUsage(request, Response.json(result), { compute: result.elapsedMs });
}

That is the complete loop: create with the CLI, author the contract as code, seed and publish, then operate at runtime — every step copy-pasteable, no browser in the hot path.

title
"Create cron job"
resource
resource
"cron_jobs"
effect
"create"
id
"cron-job.delete"
kind
"mutation"
title
"Delete cron job"
subject
type
"cron_job"
from
"path_param"
name
"id"
resource
resource
"cron_jobs"
effect
"delete"
audit
"full"
routes
"GET /v1/cron-jobs"
"POST /v1/cron-jobs"
action
"cron-job.create"
"DELETE /v1/cron-jobs/{id}"
action
"cron-job.delete"
unknown
@Plan
"starter"
name
"Starter"
price
amount
2900
currency
"usd"
interval
"month"
grants
capabilityGrant
"managed-cron"
limits
cron_jobs
10
limits
requests
rate
600
interval
"minute"
enforcement
"enforce"
unknown
@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"
unknown
void
CronCloud