SaaS.Dev

Tenants, seats and access in an App: depth

How a Customer's Tenant, each person's seat and each Persona's Access level decide what an App renders and what the Offer charges, in the design.

In the design, every person who signs in to an App works inside one Customer's Tenant, holds one seat, and acts through one Access Level. The Tenant bounds which records exist, the Access level bounds what renders, and the Offer counts the seat.

One Tenant per Customer

A Tenant is the part of a Startup's data that belongs to one Customer. Tenancy gives each one its own path, keys and Limits, and no query crosses Tenants.

In the design, an App adds nothing to that partition. Each list, picker and count reads from the signed-in person's Tenant, by the rule tenant-scope.

On leaseclock.com, Harbor Property Group is one Customer with the Tenant harbor. Its manager sees Harbor's 12 leases and never another company's, at api.sb/leaseclock.com/harbor and in the App alike. A person who belongs to two Tenants switches between them with a TenantSwitcher in the App's header.

Sign-in and the seat

A person reaches a Tenant by invitation, then signs in with a passkey, an email link or their company's single sign-on, as Auth and Let customers sign in describe. In the design, the invitation names one Persona, and the Persona names the Access level.

Each person who accepts holds one seat. The Offer's seats key decides how seats count against what the Customer pays.

offers/team.mdx
---
price: { amount: 12, currency: USD }
interval: month
seats: members
---
seatsAn accepted invitationThe members screen shows
membersadds a seat, prorated into the current period on Stripeseats used, which equals seats paid
manualpast the bought quantity, asks someone holding admin to buy a seatseats used against seats paid

Harbor's manager invites an assistant on team. The Stripe quantity on Harbor's Subscription moves from 1 to 2, and the members screen (members) reads 2 of 2. Plans inside your App covers what a seat unlocks; Charge per seat is the ask.

What each Access level renders

The registry presets four Access levels. In the design, each one bounds the screens a Persona gets for the same Noun.

Access levelGrantsLease screensMembers screen
ownerevery Verb on every Noun, one-way Verbs includedlist, record page, both forms, Renew, Terminateinvite, remove, change Access levels
adminread every Noun, every two-way change, access includedlist, record page, both forms, Renew, Terminateinvite, remove, change Access levels
memberread every Noun, two-way changes except to the Nouns that decide accesslist, record page, both forms, Renew, Terminateread only
viewerread every Noun, change nothinglist and record pageread only

A Permission narrows a preset further. Granting the assistant Lease.renew alone, with no Lease.terminate, leaves the Terminate button off every screen the assistant sees. App per Persona: derivation rules lists each rule that reads these grants.

Where the check runs

In the design, the Verb checks the caller's Permissions on every call, from any Interface. A screen only leaves out what that check refuses, so a missing button never stands in for a refusal. Permissions sets out the check.

Two facts keep this a design.

  • The Admin runs a Verb's rules in the browser, so api-enforces-permissions waits.
  • The registry defines an Access Level as a preset a Worker holds. Harbor's assistant is a person at a Customer, not a Worker of leaseclock.com, so no seat holder has an Access level to read.

Contents