Custom roles let Enterprise organizations define their own role names and permission sets. Built-in Owner and Member roles ship with every org; on Enterprise you can add as many as you need — Auditor for SOC 2 separation of duties, Reviewer for read + decision approval access, External integrator for read-only API consumers, anything that fits how your team works.Documentation Index
Fetch the complete documentation index at: https://docs.hopsule.com/llms.txt
Use this file to discover all available pages before exploring further.
Custom roles require the Enterprise plan. Non-Enterprise orgs see
the two built-in roles and a paywalled UI under Settings → Roles.
How it works
Each organization carries a per-org role catalog inOrganizationRole. Owner is sealed at the database level (one
immutable row per org) so no permission edit can lock an org out.
Member is system-flagged but editable — owners can rename it
(“Contributor”) or narrow its permissions.
Every member’s UserOrganization row carries a role_id foreign key
to the catalog. On each authenticated request, the auth middleware
resolves that role into a permission slice and attaches it to the
request context — so every handler downstream sees the same effective
set regardless of which role label the user actually holds.
Walkthrough
Open Settings → Roles
From the organization dropdown, switch to the org. Open
Settings and click Roles in the sidebar. You’ll see the two
seeded rows (Owner, Member) plus any custom roles you’ve already
created.
Create a custom role
Click Create role. A sheet opens with:
- Name — must be unique within your org (case-insensitive).
- Description — what this role is for.
- Copy permissions from — optional. Picking Member pre-fills the 13 default permissions; you tweak from there.
- Permission matrix — 47 keys across 16 domains (Decisions / Memories / Capsules / Tasks / Team / Settings / Billing / …). Each domain has Select all / Deselect all. Sensitive permissions are marked with a warning icon — be deliberate.
Assign at invite time
From Team → Invite user, the role dropdown lists every
assignable role in your org. Pick Auditor, send the invite,
and the invitee lands with that role’s exact permissions.
Built-in roles
Owner
| Permissions | All 47 keys (always full access) |
| Editable? | No — name and permissions are sealed |
| Deletable? | No — immutability is enforced at the database layer |
| Count per org | Exactly one |
Member
| Permissions | 38 standard keys (no *:delete, *:manage) |
| Editable? | Yes — name and permissions can be changed |
| Deletable? | No — every org keeps a built-in fallback row |
| Count per org | Exactly one |
org:decisions:reject, do it in the role editor. The change
applies to every existing Member-role membership immediately.
Permission reference
Each permission key follows the patternorg:<domain>:<verb>:
| Domain | Permissions |
|---|---|
decisions | create, read, update, delete, accept, reject, deprecate |
memories | create, read, update, delete |
capsules | create, read, freeze, supersede |
tasks | create, read, update, delete |
comments | create, read |
sharing | create, read, revoke |
tags | create, read, delete, attach |
chat | create, read, update, delete |
conflicts | read, resolve |
imports | start, read, cancel |
graph | read, manage |
notifications | read, manage |
team | read, invite, manage |
settings | read, manage |
billing | read, manage |
projects | create, read, update, delete |
Common recipes
Auditor — read-only access plus sharing
Auditor — read-only access plus sharing
Use this for SOC 2 audit teams, security reviewers, or external
consultants who need visibility without write access.Copy from Member, then Deselect all, then enable:
- all
org:*:readkeys org:sharing:create(so they can generate share links to capsules for downstream review)
Reviewer — decision approval only
Reviewer — decision approval only
For senior engineers who approve decisions but don’t write code.Enable:
org:decisions:read,org:decisions:accept,org:decisions:reject,org:decisions:deprecateorg:memories:read,org:capsules:read,org:tasks:readorg:comments:create,org:comments:readorg:conflicts:read,org:conflicts:resolve
External integrator — programmatic read
External integrator — programmatic read
For partner systems that consume your decision history via the API.Enable only:
org:decisions:read,org:memories:read,org:capsules:readorg:tasks:read,org:graph:readorg:projects:read
Reader-with-tags — read + categorize
Reader-with-tags — read + categorize
Tier downgrade
If your org downgrades from Enterprise back to Pro:- Existing custom roles stay — members keep their assigned permissions.
- Settings → Roles becomes read-only. A banner explains that Custom roles require Enterprise; existing assignments still apply but you can’t create, edit, or delete roles until you re-upgrade.
- Invite and change-role dropdowns still list every existing role — you can put a teammate into the existing Auditor role even on Pro.
- Re-upgrading restores write access immediately. No data migration in either direction.
API reference
All endpoints live under/organizations/{orgId}/roles. List and Get
are readable by any org member; mutations require org:settings:manage
AND Enterprise tier.
| Endpoint | Status | Notes |
|---|---|---|
GET /organizations/{orgId}/roles | 200 | Returns all rows for the org. Members can read. |
GET /organizations/{orgId}/roles/{roleId} | 200, 404 | Single role. |
POST /organizations/{orgId}/roles | 201, 402 LICENSE_REQUIRED, 409 NAME_CONFLICT | Body: {name, description, permissions}. |
PATCH /organizations/{orgId}/roles/{roleId} | 200, 403 (Owner is immutable) | Partial: any subset of name/description/permissions. |
DELETE /organizations/{orgId}/roles/{roleId} | 204, 422 (still has members) | System rows can’t be deleted. |
Authorization: Bearer <token> header
(NextAuth session token in dashboard, API token for service-to-service).
Limitations
- Owner is immutable. You can’t change Owner’s permissions or rename
it. If you need a second “super admin”, create a custom role with the
same permission set — but only one row per org has
is_immutable=true. - One Owner per org. Demoting the last Owner returns 400
MUST_HAVE_OWNER. Transfer ownership first if you need to leave. - Member can’t be deleted. It’s the fallback when a custom role is removed and the operator picks “reassign to Member” (Phase I.4).
- Cross-org role isolation. Roles are scoped by
organization_id; you can’t reference an Org A role when inviting into Org B.
FAQ
What happens to a member if I delete their role?
What happens to a member if I delete their role?
The delete endpoint returns 422 if the role still has members. You
must reassign them first (Phase I.4 ships the in-modal reassignment
flow; for now use the change-role dropdown on each member row).
Can I rename the built-in Member role?
Can I rename the built-in Member role?
Yes. Open the role editor on the Member row, change the name to
“Contributor”, save. The change applies to every existing membership
immediately — there’s no migration step.
Can custom roles span multiple orgs?
Can custom roles span multiple orgs?
No. Each role belongs to exactly one organization. If you run
multiple orgs and want the same role definition in each, create
them separately — the permission catalog is global so the keys
match.
How are permissions enforced — backend or frontend?
How are permissions enforced — backend or frontend?
Backend. The frontend asks
usePermissions().can("...") for UI
gating (hiding buttons, disabling fields), but every actual write
is checked server-side from the request’s resolved permission slice.
A user with a stale browser session can’t escalate by tinkering
with React state.