An organization is the tenant boundary. Stores, billing, credits, conversations and audit logs all belong to one, and nothing crosses between two.
A user can belong to several. The URL says which one you are in:
/<orgSlug>/... for the organization, /<orgSlug>/<storeSlug>/... for
one of its stores.
The five roles
| Role | Stores | Billing | Members | AI | |---|---|---|---|---| | Owner | read, create, edit, delete | read + manage | read, invite, remove | run, approve tools | | Admin | read, create, edit | read | read, invite | run, approve tools | | Member | read, edit | read | read | run, approve tools | | Viewer | read | read | read | — | | Delegate | nothing by default | | | |
Two rows deserve a sentence each.
Viewer cannot use the AI. It is the only role without ai.use, and
that is the point of the role: a stakeholder who should see the numbers
without being able to spend the credit balance.
Delegate has an empty baseline, by construction. It is the role for a mandated outside party: an agency, a freelancer working inside a client's organization. It holds exactly what an explicit grant gives it and nothing by virtue of existing. A guard checks every registry entry for this, because the tempting change is to add one permission "for consistency", and that is the change that would quietly open the door.
Per-member overrides
The role is a baseline, not a ceiling. OrganizationMember.permissions
carries { allow: [...], deny: [...] }: allow adds on top of the
baseline, deny strips from it. An admin edits this from the member
matrix.
Permissions are dotted strings (store.read, billing.manage,
members.invite, agent.run, ai.use, workspace.write), and the
list is open: a new one needs a registry row, not a migration.
Two families the baselines do not carry
studio.* — the creative production chain. An organization that
does not do Studio work should not hand creative powers to its members
just because they are members, so no non-owner baseline includes them.
They arrive through a Studio role preset or an explicit per-member
allow. The owner keeps them: the production is the product, and the
owner owns the brands.
agency.* — seven permissions covering prospecting, Go/No-Go gates,
production verdicts and per-brand margin. These are how BoostEcom
operates brands on behalf of third parties, not something sold to a
merchant. They are the only family excluded from the owner short-circuit
and absent from the owner baseline. Both are needed: removing one
without the other is a silent no-op, because an owner stripped of the
short-circuit would fall back to the baseline and get the permission
back.
Invitations
An invitation is a durable row, not a link you can forge.
- The email carries a one-shot token; only its sha256 hash is stored.
- It expires after 7 days.
- It is revocable instantly (
DELETE /api/organizations/invite), which kills the link. - The token is not enough. Accepting requires a signed-in session whose email matches the invited address, so a forwarded link is useless to anyone but the intended recipient.
Re-inviting the same address refreshes the row (new token, new expiry, accepted/revoked state reset — rather than piling up invitations.
The landing page is /invite, reached with ?token=.
What this replaced
The previous scheme was a tokenless ?org=&email= link. Anyone could
construct one, and there was no acceptance endpoint at all.