Farther ShoreDocs
Go to Farther Shore
The fs.business() program
Meters & measures
Counted resources
Routes & access groups
Route groups & grants
Plans & pricing
The build output
Team RBAC
Tenancy & identity
Shared-workspace dataPer-member dataCreate user state on demandCollaborationAvoid boundary confusion
Frontend integrations
@farthershore/business
Add metered routes
Add a resource limit
Add team RBAC
@farthershore/business exports
@farthershore/business/codegen exports
Status
Docs/Build your product/Tenancy & identity

Tenancy & identity

Use the subscriber organization for billing and the verified principal for data ownership.

Every customer tenancy is a subscriber organization, including a solo user. Plans, subscriptions, billing, entitlements, and business-level limits attach to that organization.

Inside it, requests carry a verified principal:

  • a member principal identifies a person;
  • a service principal identifies an organization-owned machine credential.

Do not trust customer-supplied organization or user ids in headers or request bodies. Verify the Farther Shore runtime context in the backend SDK and derive ownership from the signed principal.

Shared-workspace data

Key shared rows by the verified subscriber/organization identifier. Every query must include that key, even when another id appears globally unique.

ts
// ctx comes from fs.handler() after fs.middleware() verifies the request.
const projects = await db.project.findMany({
  where: { orgId: ctx.principal.org.id },
});

This keeps billing, entitlements, and the backend's data boundary aligned.

Per-member data

For private rows inside the organization, include both boundaries:

ts
import { requireMember } from "@farthershore/backend";

const member = requireMember(ctx);
const documents = await db.document.findMany({
  where: {
    orgId: ctx.principal.org.id,
    ownerMemberId: member.memberId,
  },
});

Require a member subject on routes that need memberId:

ts
fs.route("/v1/me/documents", {
  get: { requireMember: true },
});

Create user state on demand

Identity and webhook delivery can race with the first API request. Do not make a webhook the prerequisite for a user row. Use a database unique key on the verified identity and an atomic upsert/find-or-create path on first use.

See Storing per-user data for the transaction and retry pattern.

Collaboration

Sharing a row is a domain authorization rule in your backend. Store an explicit membership or ACL keyed by verified member ids and scope it to the subscriber. Managed RBAC answers whether a member may call an operation; it does not decide which individual document that operation may return.

Avoid boundary confusion

  • Builder organizations own Farther Shore businesses; subscriber organizations buy and use one business. They are different domains.
  • A plan grant is subscriber-wide; managed RBAC narrows member operations.
  • A service credential is not a person and has no member id.
  • Environment ids route preview data and configuration; they are not customer tenancy ids.

If production and previews share a database, isolate their rows or databases explicitly as well. Organization scoping alone is not an environment isolation strategy. Test with two organizations and two members: changing a body, URL, or query parameter to another owner's id must never widen the verified scope.

PreviousTeam RBACNextFrontend integrations

On this page

Shared-workspace dataPer-member dataCreate user state on demandCollaborationAvoid boundary confusion