Roles & Permissions
Complete reference for workspace roles, platform admin access, and permission enforcement across the 1 OAK MLS Platform
Roles & Permissions
Every workspace supports multiple members, each assigned one of four hierarchical roles. This page documents exactly what each role can access, how permissions are enforced, and the rules around member management.
Role Hierarchy
member → agent → admin → ownerEach role inherits all permissions of the roles below it. The hierarchy is enforced consistently across both apps (admin and web) using the same ordered comparison:
const ROLE_HIERARCHY: WorkspaceMemberRole[] = ['member', 'agent', 'admin', 'owner']
function hasMinimumRole(userRole, minRole): boolean {
return ROLE_HIERARCHY.indexOf(userRole) >= ROLE_HIERARCHY.indexOf(minRole)
}Role Definitions
Member
Typical persona: Transaction coordinator, assistant, or client with limited dashboard access.
- View the dashboard, My Listings, and Browse MLS pages
- View workspace members list
- View workspace settings (read-only)
- View sync run history and import task status
- Cannot edit listings, branding, website pages, or settings
Agent
Typical persona: Junior agent, marketing team member, or the primary listing agent.
Everything a member can do, plus:
- Create, edit, and manage listings and photos
- Edit landing pages
- Edit website pages (Home, Listings, About, Contact)
- Edit branding (logos, colors, fonts), agent profile, and SEO settings
- Manage service areas and compliance profiles
- View and test the MLS data source connection
- Upload assets (photos, videos, hero media)
Admin
Typical persona: Team lead, office manager, or tech-savvy operations person.
Everything an agent can do, plus:
- Invite, update, and remove team members (agents and members only)
- Edit workspace settings (name, slug, configuration)
- Manage custom domains
- Configure and run MLS sync operations
- Create, edit, and run import tasks
- Manage field mappings
- Cancel sync runs
- View system health
- Manage featured listings
Owner
Typical persona: The primary agent or brokerage principal who owns the workspace.
Everything an admin can do, plus:
- Modify or remove admins (admins cannot modify each other)
- Only role that cannot be removed by other workspace members
- Exactly one owner per workspace (set at creation time, transferred only by platform admins)
Dashboard Navigation Access
The web app sidebar filters navigation links based on the user's role. This table shows which sections are visible to each role:
| Section | member | agent | admin | owner |
|---|---|---|---|---|
| Dashboard | yes | yes | yes | yes |
| My Listings | yes | yes | yes | yes |
| Browse MLS | yes | yes | yes | yes |
| Landing Pages | -- | yes | yes | yes |
| Import Data | -- | -- | yes | yes |
| Website Pages (Home, Listings, About, Contact) | -- | yes | yes | yes |
| Branding | -- | yes | yes | yes |
| Agent Profile | -- | yes | yes | yes |
| SEO & Tracking | -- | yes | yes | yes |
| Service Areas | -- | yes | yes | yes |
| Data Source | -- | yes | yes | yes |
| Legal & Compliance | -- | yes | yes | yes |
| Team | -- | -- | yes | yes |
| Settings | -- | -- | yes | yes |
| System Health | -- | -- | yes | yes |
Source: apps/web/src/components/dashboard/dashboard-shell.tsx (lines 60-103).
Admin API Permissions
All admin API routes enforce a minimum role via requireWorkspaceAccess(workspaceId, minRole). If the user does not meet the minimum role, the route returns 403 Forbidden.
Workspace Routes (minimum role: member)
These routes allow any workspace member to read data:
| Route | Method | Min Role | Description |
|---|---|---|---|
/api/admin/workspaces/[id] | GET | member | Get workspace details |
/api/admin/workspaces/[id]/settings | GET | member | Get workspace settings |
/api/admin/workspaces/[id]/members | GET | member | List workspace members |
/api/admin/workspaces/[id]/invitations | GET | member | List invitations |
/api/admin/workspaces/[id]/imports | GET | member | List import tasks |
/api/admin/workspaces/[id]/sync/runs | GET | member | List sync runs |
/api/admin/workspaces/[id]/sync/runs/[runId] | GET | member | Get sync run details |
/api/admin/workspaces/[id]/listings/lookup | GET | member | Look up listing |
/api/admin/workspaces/[id]/mls/test | POST | member | Test MLS connection |
/api/admin/workspaces/[id]/usage | GET | member | Get workspace usage stats |
Workspace Routes (minimum role: admin)
These routes require admin or owner for write operations:
| Route | Method | Min Role | Description |
|---|---|---|---|
/api/admin/workspaces/[id] | PATCH | admin | Update workspace |
/api/admin/workspaces/[id]/settings | PATCH | admin | Update settings |
/api/admin/workspaces/[id]/members/[memberId] | PATCH | admin | Update member role |
/api/admin/workspaces/[id]/members/[memberId] | DELETE | admin | Remove member |
/api/admin/workspaces/[id]/invitations/[invitationId] | DELETE | admin | Cancel invitation |
/api/admin/workspaces/[id]/domains | POST | admin | Add domain |
/api/admin/workspaces/[id]/domains/[domainId] | DELETE | admin | Remove domain |
/api/admin/workspaces/[id]/mls | GET | admin | Get MLS connection details |
/api/admin/workspaces/[id]/mls | POST | admin | Create/update MLS connection |
/api/admin/workspaces/[id]/sync/run | POST | admin | Trigger sync run |
/api/admin/workspaces/[id]/sync/runs/[runId]/cancel | POST | admin | Cancel sync run |
/api/admin/workspaces/[id]/imports/[taskId]/run | POST | admin | Run import task |
/api/admin/workspaces/[id]/imports/[taskId]/toggle | POST | admin | Enable/disable import task |
/api/admin/workspaces/[id]/mapping | POST | admin | Update field mapping |
/api/admin/workspaces/[id]/mapping/validate | POST | admin | Validate field mapping |
/api/admin/workspaces/[id]/mapping/[versionId]/restore | POST | admin | Restore field mapping version |
Web App Dashboard API Permissions
The web app uses canWrite() (agent+), isAgent() (agent+), and isAdmin() (admin+) for write operations:
| Operation | Required Check | Minimum Role |
|---|---|---|
| Edit listings, photos, reorder photos | canWrite | agent |
| Edit branding, upload logos | canWrite | agent |
| Edit compliance profile, MLS logo | canWrite | agent |
| Edit service areas, reorder | canWrite | agent |
| Edit hero media, upload videos | canWrite | agent |
| Edit website pages (hero sections) | canWrite | agent |
| Upload agent avatar | canWrite | agent |
| Create/edit import tasks | canWrite | agent |
| Edit landing pages | isAgent | agent |
| Configure MLS connection | isAgent | agent |
| Test MLS connection | isAgent | agent |
| Manage members (list, update, remove) | isAdmin | admin |
| Manage invitations (create, cancel) | isAdmin | admin |
| Manage domains (add, remove) | isAdmin | admin |
| Update system settings (features, comingSoon, mapbox) | isAdmin | admin |
| Update content settings (pages, branding, profile, SEO) | canWrite | agent |
| Delete import tasks | isAdmin | admin |
| Toggle featured listings | isAdmin | admin |
Platform Admin Routes
These routes are entirely separate from workspace membership and require platform admin status:
| Route | Method | Min Platform Role | Description |
|---|---|---|---|
/api/admin/platform-admins | GET | admin | List platform admins |
/api/admin/platform-admins | POST | super_admin | Add platform admin |
/api/admin/platform-admins/[adminId] | PATCH | super_admin | Update platform admin role |
/api/admin/platform-admins/[adminId] | DELETE | super_admin | Remove platform admin |
/api/admin/users | GET | admin | List all users |
/api/admin/users/[userId] | GET | admin | Get user details |
/api/admin/workspaces/[id]/transfer-ownership | POST | admin | Transfer workspace ownership |
/api/admin/compliance-logos | GET, POST | admin | Manage shared compliance logos |
/api/admin/settings | GET, PUT | admin | Platform-wide settings |
/api/admin/demo/seed | POST | admin | Seed demo data |
Member Management Rules
Who Can Invite
Only admin and owner roles can send invitations, enforced by the canManageMembers() check:
function canManageMembers(role: WorkspaceMemberRole): boolean {
return role === 'owner' || role === 'admin'
}Invitable Roles
The owner role cannot be assigned via invitation. The invitation role constraint in the database enforces this:
CHECK (role IN ('admin', 'agent', 'member'))Role Modification Rules
The canModifyMember() function controls who can change or remove whom:
| Actor | Can Modify |
|---|---|
| owner | admin, agent, member |
| admin | agent, member |
| agent | nobody |
| member | nobody |
Key constraints:
- Owners cannot modify other owners -- prevents removing the workspace owner
- Admins cannot modify other admins -- prevents lateral privilege escalation
- Nobody can self-demote below their target's role -- enforced by the actor/target comparison
Owner Protection
- There is exactly one owner per workspace (set during workspace creation)
- Owners cannot be removed or demoted through the member management API
- Ownership can only be transferred by a platform admin via the
/transfer-ownershipendpoint
Platform Admin
Platform admin is a separate access system, independent of workspace membership.
How It Works
- Platform admins are stored in the
platform_adminstable (notworkspace_members) - When a platform admin accesses any workspace, they receive owner-level permissions regardless of whether they are a workspace member
- The
isPlatformAdminflag on the auth result distinguishes platform admin access from actual workspace membership
Platform Role Hierarchy
admin < super_admin| Platform Role | Capabilities |
|---|---|
admin | Access all workspaces as owner, manage users, view platform settings, transfer workspace ownership |
super_admin | Everything above, plus grant/revoke platform admin access to other users |
Granting Platform Admin
Only a super_admin can grant or revoke platform admin status via the platform admins API. This prevents privilege escalation -- a platform admin cannot promote themselves to super_admin.
Related Documentation
- Security Architecture -- Authentication, RLS, token encryption, and rate limiting
- API Reference -- Full endpoint documentation
- Database Schema -- Table definitions and constraints