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) orSUBTREE(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.
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
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
SUBTREErole 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.