Skip to main content

Organizations & isolation

The organization tree​

An organization is a tenant. Organizations form a tree: a parent org can have child orgs (departments, franchisees, downstream customers), each with their own users, roles, and applications.

  • A role's scope decides how far it reaches: SELF (this org only) or SUBTREE (this org and everything below it).
  • A parent-org admin can administer its children; a child cannot see up or sideways.

Every organization carries a materialized path (e.g. /1000001/57/903/) so ancestry is a single indexed lookup, not a tree walk.

Tenant isolation​

Isolation is enforced at the database with PostgreSQL row-level security (RLS), keyed on tenant_id — not by application-layer WHERE clauses that a bug could forget. Every request runs under the caller's tenant context; the database itself refuses to return another tenant's rows.

The runtime connects as a non-superuser role so RLS actually applies (a superuser would bypass it). Trusted, user-less system operations — login before a tenant is known, cross-tenant automation — run under an explicit, audited bypass, never implicitly.

Why this matters

For a regulated tenant, "we filter by tenant in the query" is not a control an auditor accepts. RLS makes cross-tenant leakage a database-level impossibility, not a code-review promise.

White-labeling​

Regulated plan

Marking an organization white-labelled, and branding it, are part of the by-arrangement plan tier — contact us to enable them.

A normal child organization stays inside its parent's tenant — same tenant_id, same isolation boundary, just another node in the tree. A white-labelled organization is different: it keeps its place in the tree (same parent, same path prefix) but is provisioned as its own tenant, fixed permanently at creation. From that point RLS separates it from the branch above exactly as it separates two unrelated customers — the org that created it cannot read its data by simply being its ancestor.

What the parent does keep, because reach in this service is resolved from the org tree's path rather than from tenant_id:

  • Subtree administration. A SUBTREE role held at (or above) the branch's node still authorizes acting on it — creating its first admin, suspending it, running a bulk import to onboard its data. This is what makes "resell the platform to your own customers" a real workflow: the reseller provisions and onboards a branch's data before handing it off, the same way it would provision a plain child org.
  • Application roles. The branch is running someone else's product — its applications, their permission vocabulary, and the roles shipped with them still belong to whoever created it. A branch's assignable-roles list (GET /api/roles/assignable) therefore includes the product owner's application roles too, resolved via the org that pays for the branch's subscription — not just what the branch's own tenant has defined. Only the product owner may edit those shared definitions, though: a branch admin sees them (so they know what a user there could hold) but cannot reach into a role every other customer of that application shares.
  • Billing. Isolation moves to the branch; billing does not. Usage is counted against whoever pays for it.

Branding​

Product name, logo, accent color, mail-from name, and support email — what a white-labelled slice of the tree shows a user, and what a password-reset email looks like it came from. Resolution walks up the tree: the nearest ancestor with a brand set wins, so branding it once at a customer's root covers every branch beneath, while a branch can still override it for itself. An unbranded tree falls back to the platform's own name — never a blank or broken page.

Console: Branding in the sidebar. API: GET/PUT /api/branding/organizations/{organizationId}.

Reaching organizations via the API​

  • GET /api/organizations — the orgs within your reach (yours and below), keyset-paginated.
  • GET /api/organizations/search?q=… — search by name/slug.
  • POST /api/organizations — create a child (parentId).
  • POST /api/organizations/{id}/suspend · …/activate — lock a tenant out or restore it.

Suspending an org (or any org above it) stops its users from obtaining tokens — this is what makes a subscription lapse or an offboarding actually bite.