Lumio uses role-based access control (RBAC) to manage what each team member can do within an account.
How It Works
Each account has roles (e.g., Owner, Moderator, Viewer). Each role has a set of permissions. When a user performs an action, the system checks if their role grants the required permission.
Permission checks run on all four protocol surfaces with the same resource:action strings:
- REST —
require_permission() in route handlers
- GraphQL —
PermissionGuard on resolvers
- WebSocket — channel-subscribe gate in
crates/lo-websocket/src/gate.rs
- Frontend —
<Gate permission> and hasPerm() in client code
The WebSocket layer maps each channel type to one of several gate kinds — a resource:action permission on the account in the channel name, a user-scoped permission, plain account scoping, a bound token type, or public. Most account-scoped streams use a :read permission (chat:\{account_id\} → chat:read, events:\{account_id\} → events:read). See WebSocket → RBAC gate for the full mapping. A caller that fails the gate receives code: "UNAUTHORIZED" on subscribe.
Default Roles
Four roles are seeded for every new account, in this sort order: Owner, Administrator, Moderator, Viewer.
Owner (owner)
Full account access. System role (is_system: true, cannot be deleted). Holds every account permission — the list is loaded explicitly, not via a wildcard.
Administrator (administrator)
Everything Owner holds except account:delete, plan:read, plan:edit, and extension-dev:payouts. Account dissolution and plan changes are additionally enforced through an owner_id check rather than a permission.
Moderator (moderator)
Chat moderation, event monitoring, Spotify playback control, and read access across the rest of the account.
| Category | Permissions |
|---|
| Events | events:read, events:create, events:userinfo |
| Overlays | overlays:read |
| Spotify | spotify:read, spotify:playback, spotify:queue, spotify:playlist, spotify:device |
| Chat | chat:read, chat:write, chat:userinfo, chat:delete, chat:ban, chat:timeout, chat:notes, chat:raid, chat:refresh_user, chat:poll, chat:prediction |
| Connections | connections:read |
| Uploads | uploads:read |
| Rewards | rewards:read |
| Automations | automations:read, automations:execute |
| Members & Roles | members:read, roles:read |
| OBS | obs:read |
| Copyright | copyright:vote, copyright:report, copyright:recommend |
| Bot | bot-modules:read, bot-connections:read, bot-commands:read |
| Extensions | extension-store:read, extension-store:review, extension-data:read |
| Widgets | widgets:read |
| Sounds | sounds:read, sounds:play |
| Stream History | history:read, history:export |
| Public Stats | public-stats:read |
Viewer (viewer)
Read-only access.
| Category | Permissions |
|---|
| Events | events:read, events:userinfo |
| Overlays | overlays:read |
| Bot Commands | bot-commands:read |
| Extensions | extension-store:read, extension-store:review, extension-data:read |
| Widgets | widgets:read |
| Sounds | sounds:read |
Custom roles can be created under Dashboard > Settings > Roles.
Permission Reference
The registry holds 123 account permissions. A couple are assignable and seeded on the default roles but the API does not re-check them — the dashboard <Gate permission> hides the UI, but the endpoints do not gate on them, so assigning or withholding them changes nothing on the API surface:
| Permission | Where it takes effect |
|---|
events:userinfo, connections:edit | Dashboard only — <Gate permission> hides the UI; the API does not re-check them |
sessions:read/sessions:delete, automations:history, and events:delete were removed from the account registry in ZAF-1094 — they gated nothing (session management is self-scoped, there is no automation-history surface, and only the system retention sweep deletes events). copyright:report/copyright:recommend are not unenforced: they carry a PermissionGuard on the GraphQL mutations createCopyrightReport / createSafeRecommendation. There is simply no dedicated REST route — reporting and recommending both go through POST /v1/copyright/vote under copyright:vote.
The account role editor (GET /v1/roles/permissions and the GraphQL accountPermissions query) exposes exactly the account permissions listed below — the picker and the create/update validation registry share one source of truth (get_all_account_permission_infos()), so every permission you can assign is visible and vice-versa (ZAF-231). The admin panel's per-user and per-account permission-override pickers draw from the same source via the adminAssignablePermissions query (proxied as GET /api/assignable-permissions), so they list this exact set too and cannot drift (ZAF-430).
Events
| Permission | Description |
|---|
events:read | View events, notifications, and event history |
events:create | Send test events and manage notifications |
events:userinfo | View user cards and profiles from event entries |
Overlays
| Permission | Description |
|---|
overlays:read | View overlay configurations |
overlays:create | Create new overlays |
overlays:edit | Edit existing overlays |
overlays:delete | Delete overlays |
overlays:access-read | Read per-overlay access entries/candidates and see all overlays (access-manager visibility) |
overlays:access-grant | Grant (set) per-overlay access for a user |
overlays:access-revoke | Revoke per-overlay access for a user |
| Permission | Description |
|---|
widgets:read | View widget instances |
widgets:create | Create and duplicate widget instances |
widgets:edit | Edit widget configuration |
widgets:delete | Delete widget instances |
widgets:access-read | Read per-widget access entries/candidates and bypass per-widget restrictions (access-manager visibility) |
widgets:access-grant | Grant (set) per-widget access for a user |
widgets:access-revoke | Revoke per-widget access for a user |
Extension Data
Gates direct account-actor calls to an installed extension's server functions (dashboard editor, lumio run CLI). Widget runtime calls authenticated by an Extension Token are governed by the install's own scope and are not gated by these permissions.
| Permission | Description |
|---|
extension-data:read | Read installed-extension data via query functions (queryRows, query-category handlers) |
extension-data:edit | Mutate installed-extension data via insertRow / patchRow / deleteRow and mutation/action handlers |
Spotify
| Permission | Description |
|---|
spotify:read | View now playing state, queue, devices, playlists, and search tracks |
spotify:playback | Control playback: play, pause, skip, previous, seek, shuffle, repeat |
spotify:volume | Change playback volume (gates the volume slider in the dashboard and popout player) |
spotify:queue | Add tracks to the playback queue |
spotify:playlist | Manage playlists: create, rename, delete, add/remove tracks, start playlist playback |
spotify:device | Transfer playback between Spotify devices |
spotify:worker | Start and stop the manual Spotify worker connect |
Chat
| Permission | Description |
|---|
chat:read | View chat messages, history, and emotes |
chat:write | Send chat messages |
chat:userinfo | View user profiles, follow status, and moderation log |
chat:delete | Delete chat messages |
chat:ban | Ban and unban users |
chat:timeout | Timeout users |
chat:notes | View and manage user notes |
chat:raid | Cancel active raids |
chat:poll | End active polls |
chat:prediction | End active predictions |
chat:refresh_user | Manually refresh a user's platform profile data |
Connections
| Permission | Description |
|---|
connections:read | View connected platforms and credentials |
connections:create | Connect new platforms |
connections:edit | Update connection credentials |
connections:delete | Disconnect platforms |
Settings
| Permission | Description |
|---|
settings:read | View account settings |
settings:edit | Modify account settings |
Automations
| Permission | Description |
|---|
automations:read | View automations and their configuration |
automations:create | Create new automations |
automations:edit | Edit automation metadata, nodes, edges |
automations:delete | Delete automations |
automations:execute | Manually trigger and start/stop automations |
Members
| Permission | Description |
|---|
members:read | View the members list and all invitations |
members:create | Create invites, search users for direct invites |
members:edit | Change member roles, create/edit/delete custom roles |
members:delete | Remove members from the account, revoke pending invites |
Uploads
| Permission | Description |
|---|
uploads:read | View uploaded files |
uploads:create | Upload new files |
uploads:delete | Delete uploaded files |
Rewards
| Permission | Description |
|---|
rewards:read | View channel point rewards |
rewards:create | Create new rewards |
rewards:edit | Edit existing rewards |
rewards:delete | Delete rewards |
Tokens
| Permission | Description |
|---|
tokens:read | View popout tokens |
tokens:create | Create popout tokens |
tokens:edit | Edit popout tokens |
tokens:delete | Revoke popout tokens |
API Keys
Personal user API keys (lm_usr_…) and account service keys (lm_svc_…) share one dedicated permission family, separate from popout tokens.
| Permission | Description |
|---|
apikeys:read | View API keys (personal lm_usr_ and account service lm_svc_) |
apikeys:create | Create an API key (personal or account service key) |
apikeys:edit | Rename an API key (label only) |
apikeys:delete | Revoke an API key |
tokens:create, tokens:edit and apikeys:create only let a caller mint or edit a token/key within their own permissions: it can never be granted a permission the caller does not currently hold. Requesting one is rejected with FORBIDDEN, identically on GraphQL and REST. A resource:* grant (system keys only) lets the caller grant any action under that resource; a bare *:* / * is not a global wildcard (it grants nothing beyond a literal *:* / *).
Account
| Permission | Description |
|---|
account:read | View account settings and information |
account:edit | Edit account settings (name, etc.) |
account:delete | Dissolve (permanently delete) the account |
Plan
| Permission | Description |
|---|
plan:read | View account plan and billing information |
plan:edit | Change account plan, manage subscription |
Login Assignments
Assignable to account roles, but enforced at the user level: own assignments are always allowed for any authenticated user, and these permissions only gate actions taken on behalf of other users on the account.
| Permission | Description |
|---|
login-assignments:read | View login assignments for the active account. Own assignments are always visible without this permission. |
login-assignments:create | Assign a login connection to an account on behalf of another user. Own assignments are always allowed without this permission. |
login-assignments:delete | Remove a login assignment for the active account on behalf of another user. Own assignments are always removable without this permission. |
Sessions
Session management is not an account-role permission. Every authenticated user can view and revoke their own active browser sessions regardless of account role (and with no active account selected). The surface is own-scoped on both GraphQL (sessions / deleteSession / deleteAllOtherSessions) and REST (GET / DELETE /v1/users/me/sessions): the acting user's own id selects the rows, and the only gate is the first-party principal check (ZAF-469 / ZAF-516). The former sessions:read / sessions:delete role permissions were removed in ZAF-1094 — they gated nothing.
Extension Developer
Extension developer permissions gate access to the developer dashboard and extension management. They are assignable to account roles and are also exercised through developer team RBAC. See Team-Scoped Permissions for the team permission model.
| Permission | Description |
|---|
extension-dev:read | View extension developer dashboard and owned extensions |
extension-dev:create | Create new extensions |
extension-dev:edit | Edit owned extensions (metadata, code, assets) |
extension-dev:delete | Delete owned extensions |
extension-dev:publish | Submit extensions for review and publish approved versions |
extension-dev:analytics | View extension analytics (installs, usage, ratings) |
extension-dev:payouts | Manage extension revenue payouts |
Sounds
| Permission | Description |
|---|
sounds:read | View sounds |
sounds:create | Upload sounds |
sounds:edit | Edit sound metadata |
sounds:delete | Delete sounds |
sounds:play | Play sounds in browser sources |
Bot Modules
| Permission | Description |
|---|
bot-modules:read | View bot module configurations |
bot-modules:edit | Edit bot module configurations |
Copyright
| Permission | Description |
|---|
copyright:read | View safe/blocked songs, copyright check results |
copyright:edit | Add to this account's safe/blocked lists, import playlists |
copyright:delete | Remove entries from safe/blocked lists |
copyright:vote | Vote on community copyright-status candidates |
copyright:report | Report a song's copyright status — GraphQL createCopyrightReport (PermissionGuard); no dedicated REST route (reporting goes through POST /v1/copyright/vote under copyright:vote) |
copyright:recommend | Recommend songs for the global list — GraphQL createSafeRecommendation (PermissionGuard); no dedicated REST route (recommending goes through POST /v1/copyright/vote under copyright:vote) |
copyright:moderate is not an account permission — it is admin-scope (platform operator). See Admin: Copyright Moderation. It was moved out of account scope in ZAF-742 because approving/dismissing vote candidates writes the account-less global catalogue.
OBS
| Permission | Description |
|---|
obs:read | View OBS integration configuration and status |
obs:edit | Edit OBS integration settings and remote control |
obs:delete | Delete the account's OBS integration configuration |
Roles
| Permission | Description |
|---|
roles:read | View account roles and the assignable-permission catalog |
roles:edit | Create and edit custom account roles |
roles:delete | Delete custom account roles (system roles are protected server-side) |
Role create/update validates every permission string against the account registry. A wildcard (resource:* / *:*) is rejected on both REST and GraphQL and can never be persisted into an account role.
Bot Commands
| Permission | Description |
|---|
bot-commands:read | View chat commands and their per-platform overrides |
bot-commands:create | Create chat commands |
bot-commands:edit | Edit chat commands and set overrides |
bot-commands:delete | Delete chat commands and clear overrides |
Bot Connections
| Permission | Description |
|---|
bot-connections:read | View the account's custom bot identities |
bot-connections:create | Start and complete the custom bot OAuth flow |
bot-connections:delete | Remove a custom bot identity |
Declared in both account and admin scope with the same string; the admin-scope copy gates system-level bot connections through require_admin_permission.
StreamElements
| Permission | Description |
|---|
se-tokens:read | View stored StreamElements JWT tokens |
se-tokens:create | Store a StreamElements JWT token |
se-tokens:delete | Delete a stored StreamElements JWT token |
Extension Store
| Permission | Description |
|---|
extension-store:read | Browse the extension store and view installed extensions |
extension-store:install | Install an extension into the account |
extension-store:uninstall | Uninstall an extension from the account |
extension-store:configure | Change an installed extension's configuration |
extension-store:review | Write store reviews and ratings |
Stream History
| Permission | Description |
|---|
history:read | View stream/session history, reports and stats |
history:share | Create, extend and revoke shareable report links |
history:export | Export history reports (CSV/PDF/TXT/JSON) |
history:delete | Delete stored history sessions |
Default roles: Owner and Administrator hold all four; Moderator holds history:read + history:export; Viewer holds none. Gated behind the feature:stream_history feature flag.
Public Stats
| Permission | Description |
|---|
public-stats:read | View this account's channel publicness settings for the public Stats app |
public-stats:edit | Toggle a channel's publicness / chatter-leaderboard suppression (opt-out / opt-in) |
Default roles: Owner and Administrator hold both; Moderator holds public-stats:read; Viewer holds none. Gated behind the feature:public_stats_page feature flag.
Crawler control moved to admin scope (ZAF-725). The five crawler:*
permissions that steer the platform-wide stats crawler are admin-scope
(platform-operator) permissions, not account permissions — see
Admin: Crawler below.
Audit Log
| Permission | Description |
|---|
audit-log:read | Read this account's audit log (GET /v1/account/audit-log / accountAuditLog) — role/permission changes, member-role assignment, channel connections |
Default roles: Owner and Administrator hold it; Moderator and Viewer hold none. This account-scope audit-log:read is a distinct permission from the identically-named admin-scope audit-log:read (see Admin: Support, Audit & StreamElements): the account grant reads a single account's log through require_permission, the admin grant reads the operator audit surface through require_admin_permission, and holding one never unlocks the other.
Admin-Scope Permissions
Admin-scope permissions are checked against a user's admin role (not their account role). They gate the Lumio admin panel — /admin. The dashboard entry gate is admin:access, which is automatically included in every admin role.
Note: Some permissions (marked below) share an identical string with an account-scope permission but are enforced in a different context (require_admin_permission vs require_permission). This mirrors the bot-connections:* precedent.
Admin: Core Access
| Permission | Description |
|---|
admin:access | Dashboard entry gate. Auto-injected into every admin role. |
Admin: Role Management
| Permission | Description |
|---|
admin-roles:read | View admin roles, their permissions, and member lists |
admin-roles:create | Create new admin roles |
admin-roles:edit | Edit role name, description, permissions, and user assignments |
admin-roles:delete | Delete admin roles (is_system roles are protected server-side) |
Admin: Privacy
| Permission | Description |
|---|
admin:privacy-erase | Erase persisted personal data on subject request (GDPR Art. 17). Powers DELETE /v1/admin/privacy/youtube/member/{id} (clears a YouTube member's cached records and their historical chat rows across all accounts) and POST /v1/admin/privacy/chat/erase (erases a data subject's platform_chat_messages across all accounts, by Lumio user id or platform+user_id). Reserved for support tooling; assigned only to system_admin. |
Admin: Developer Verifications
| Permission | Description |
|---|
developer-verification:read | View developer applications and their details |
developer-verification:edit | Approve or reject developer applications |
Admin: Feature Flags
| Permission | Description |
|---|
features:read | View feature flags |
features:edit | Toggle feature flags |
feature-flags:read | View the feature-flag registry detail view |
feature-flags:edit | Edit feature-flag metadata (category, description, plan defaults) |
Admin: Users & Accounts
| Permission | Description |
|---|
users:read | View users, login connections, and membership details |
users:edit | Edit user display name, email, overrides, and manage login connections |
users:delete | Delete users |
accounts:read | View account details, limits, features |
accounts:edit | Update account name, plan, limits, feature overrides, members, reconnect flags, and primary connections |
accounts:delete | Delete accounts |
accounts:overrides-read | Read per-account permission overrides |
accounts:overrides-edit | Set or remove per-account permission overrides |
| Permission | Description |
|---|
providers:read | View platform provider configurations |
providers:edit | Toggle providers and sub-flags |
platforms:read | View platform connection configuration |
platforms:edit | Edit platform connection configuration |
Admin: Infrastructure
| Permission | Description |
|---|
system-keys:read | View system API keys |
system-keys:create | Create system API keys |
system-keys:delete | Delete system API keys |
oauth-clients:read | View OAuth client registrations |
oauth-clients:create | Create OAuth clients |
oauth-clients:edit | Edit OAuth clients |
oauth-clients:delete | Delete OAuth clients |
system-connections:read | View system-level platform connections |
system-connections:edit | Create and update system-level platform connections |
system-connections:delete | Delete system-level platform connections |
Admin: Billing, Plans & Coupons
| Permission | Description |
|---|
billing:read | View billing data |
billing:edit | Modify billing data |
plans:read | View plans |
plans:create | Create plans |
plans:edit | Edit plans and their feature mappings |
plans:delete | Delete plans |
subscriptions:read | View subscriptions |
subscriptions:edit | Modify subscriptions |
coupons:read | View coupons |
coupons:create | Create coupons |
coupons:edit | Edit coupons |
coupons:delete | Delete coupons |
Admin: Bot Control & Commands
| Permission | Description |
|---|
bot-control:read | View bot enabled/disabled status across all accounts |
bot-control:edit | Toggle the bot on or off for an account |
bot-commands:read | View chat commands across all accounts |
bot-commands:create | Create chat commands on any account |
bot-commands:edit | Edit chat commands on any account |
bot-commands:delete | Delete chat commands on any account |
bot-connections:read | View system bot connections |
bot-connections:create | Create system bot connections |
bot-connections:delete | Delete system bot connections |
discord-guilds:read | View Discord guild connections across all accounts |
discord-guilds:delete | Delete a Discord guild connection |
Admin: Support, Audit & StreamElements
| Permission | Description |
|---|
support:read | View support tickets |
support:edit | Handle support tickets |
audit:read | Read the audit log |
audit-log:read | Read the admin (system-scope) audit log (GET /v1/admin/audit-log). Distinct from the identically-named account-scope audit-log:read, which reads a single account's log via require_permission; this admin copy is checked via require_admin_permission and holding one never unlocks the other. |
csp-reports:read | Read the CSP-violation calibration aggregate — cspViolationReports / REST GET /v1/admin/csp-reports. Distinct (violated_directive, blocked_host) pairs with counts, from the capped csp_violation_reports TimescaleDB table (≤5000 pairs, ≤30 d rolling). Admin-scope (platform operator) — seeded to system_admin, not assignable to any tenant role (ZAF-1051). |
se-tokens:read | View StreamElements tokens across all accounts |
se-tokens:delete | Delete a StreamElements token on any account |
Admin: Extensions & Developers
| Permission | Description |
|---|
extension-review:read | View the extension review queue and submission details |
extension-review:edit | Approve, reject, or request changes on extension submissions |
developer-limits:read | View developer rate limits and quotas |
developer-limits:edit | Edit developer rate limits and quotas |
Admin: Ideas Moderation
| Permission | Description |
|---|
ideas:moderate_read | View all ideas with moderation details (reports, flags, internal notes) |
ideas:moderate_status | Change idea status (approve, reject, mark as planned, close) |
ideas:moderate_edit | Edit any idea regardless of ownership |
ideas:moderate_delete | Delete any idea regardless of ownership |
ideas:moderate_comment | Delete any comment on ideas regardless of ownership (comment editing is author-only) |
Admin: Copyright Moderation
| Permission | Description |
|---|
copyright:moderate | Approve/dismiss community vote candidates into the global cross-account safe/blocked catalogue (approveReport/dismissReport + REST /v1/copyright/vote-candidates/{track_id}/approve·/dismiss). Admin-scope (platform operator) — seeded to system_admin, not assignable to any tenant role (ZAF-742). |
Admin: User Roles
| Permission | Description |
|---|
user-roles:read | View user roles and their permissions |
user-roles:create | Create new user roles |
user-roles:edit | Edit user role name, description, permissions, and assignments |
user-roles:delete | Delete user roles (is_system roles are protected server-side) |
Admin: Crawler
Platform-operator control of the public stats crawler — a single global
resource (one crawler, one global scope flip). Moved from account scope to admin
scope in ZAF-725; enforced via require_admin_permission / AdminPermissionGuard
and seeded to the system_admin role only.
| Permission | Description |
|---|
crawler:scope-read | Read the public stats crawler's scope mode (GET /v1/crawler/scope / crawlerScope) |
crawler:scope-edit | Set the crawl scope mode, incl. the opt-in global flip (PUT /v1/crawler/scope / setCrawlerScope) |
crawler:watchlist-read | List the crawl watchlist (GET /v1/crawler/watchlist / crawlerWatchlist) |
crawler:watchlist-create | Add a channel to the crawl watchlist (POST /v1/crawler/watchlist / addCrawlerWatchlistChannel) |
crawler:watchlist-delete | Remove a channel from the crawl watchlist (DELETE /v1/crawler/watchlist/{platform}/{platform_channel_id} / removeCrawlerWatchlistChannel) |
Actions are granular (the coarse :manage is disallowed); each scope change or watchlist mutation emits a system-scope audit event. The Admin app additionally gates entry on admin:access.
Admin: Shared with Account Scope
Seventeen permission strings are declared in both admin scope and account scope. They share the same string value but are enforced by different guards (require_admin_permission against the user's admin role vs require_permission against their account role), so holding one does not grant the other:
| Permissions | Category |
|---|
copyright:read, copyright:edit, copyright:delete | Copyright |
obs:read, obs:edit | OBS |
bot-modules:read, bot-modules:edit | Bot Modules |
bot-commands:read, bot-commands:create, bot-commands:edit, bot-commands:delete | Bot Commands |
bot-connections:read, bot-connections:create, bot-connections:delete | Bot Connections |
se-tokens:read, se-tokens:delete | StreamElements |
audit-log:read | Audit Log |
User-Scoped Permissions
User-scoped permissions are global — they are not tied to a specific account. They control access to platform-wide features that exist outside the account context, such as the Ideas Hub. User-scoped permissions are assigned via user roles (user_roles / user_role_permissions), not account roles. Enforcement uses auth.require_user_permission() on the backend.
Ideas Hub
| Permission | Description |
|---|
ideas:read | Read ideas |
ideas:create | Create new ideas |
ideas:edit | Edit own ideas |
ideas:delete | Delete own ideas |
ideas:vote | Vote on ideas |
ideas:comment_read | Read comments on ideas |
ideas:comment_create | Create comments on ideas |
ideas:comment_edit | Edit own comments |
ideas:comment_delete | Delete own comments |
ideas:comment_vote | Vote on comments and replies |
Default User Roles
Three system user roles are seeded by default:
Member
Assigned to all users automatically. Grants full Ideas Hub participation.
| Category | Permissions |
|---|
| Ideas | ideas:read, ideas:create, ideas:edit, ideas:delete, ideas:vote |
| Comments | ideas:comment_read, ideas:comment_create, ideas:comment_edit, ideas:comment_delete, ideas:comment_vote |
Restricted
Limited to read-only access.
| Category | Permissions |
|---|
| Ideas | ideas:read |
| Comments | ideas:comment_read |
Moderator
Full user permissions plus admin-level moderation capabilities. The five ideas:moderate_* permissions are admin-scoped — see Admin: Ideas Moderation above.
| Category | Permissions |
|---|
| Ideas | ideas:read, ideas:create, ideas:edit, ideas:delete, ideas:vote |
| Comments | ideas:comment_read, ideas:comment_create, ideas:comment_edit, ideas:comment_delete, ideas:comment_vote |
| Moderation (admin-scoped) | ideas:moderate_read, ideas:moderate_status, ideas:moderate_edit, ideas:moderate_delete, ideas:moderate_comment |
Team-Scoped Permissions (Developer Teams)
Team-scoped permissions control what each member can do within a developer team. They are separate from both account-scope and admin-scope permissions and are defined in crates/lo-auth/src/rbac.rs::team. Enforcement uses require_team_permission() in REST handlers and an equivalent helper in GraphQL resolvers. Permissions are cached in Redis per (team_id, user_id) pair.
Extension Development
| Permission | Description |
|---|
team-extensions:create | Create extensions within the team |
team-extensions:edit | Edit team extensions (metadata, code, assets) |
team-extensions:delete | Delete team extensions |
team-extensions:publish | Submit extensions for review and release |
team-extensions:analytics | View extension analytics (installs, usage, ratings) |
team-versions:create | Create new extension versions |
team-versions:edit | Edit extension versions |
Secrets
| Permission | Description |
|---|
team-secrets:read | Read team secrets (API keys, webhooks) |
team-secrets:edit | Edit team secrets |
Testers
| Permission | Description |
|---|
team-testers:read | Read the list of extension testers |
team-testers:invite | Invite extension testers |
team-testers:remove | Remove extension testers |
Team Members
| Permission | Description |
|---|
team-members:read | Read team member list |
team-members:invite | Invite new team members |
team-members:edit | Edit team member roles |
team-members:remove | Remove team members |
Team Settings
| Permission | Description |
|---|
team-settings:read | Read team settings |
team-settings:edit | Edit team settings |
Payouts
| Permission | Description |
|---|
team-payouts:read | Read team payout information |
team-payouts:edit | Edit team payout configuration |
Default Team Roles
Three roles are seeded for every new developer team:
Owner (team-owner)
System role (cannot be deleted). All 20 team permissions.
Admin (team-admin)
Manage extensions and team members. All permissions except team-settings:edit, team-payouts:edit, and team-members:remove.
Member (team-member)
View and develop extensions. Permissions: team-extensions:create, team-extensions:edit, team-extensions:analytics, team-versions:create, team-members:read, team-secrets:read, team-settings:read.
Popout Tokens
Popout tokens inherit specific permissions when created. A popout token can only have permissions that the creating user's role grants. For example, a Moderator cannot create a popout token with settings:edit.
Overlay Tokens
Overlay tokens (lm_overlay_*) have no RBAC permissions. They cannot access any REST or GraphQL endpoint. Their sole capability is subscribing to the overlay:{key} WebSocket channel where the token's bound overlay_id matches the resolved overlay key.
has_permission(_) always returns false
- Cannot broadcast to any channel
- Cannot subscribe to any channel other than the one matching their bound overlay
- Rate-limited at 600 requests/min (same tier as Popout tokens)
Shared Overlay Tokens
Shared overlay tokens (lm_share_*) provide temporary, time-limited access to a specific overlay. They are designed for sharing overlays with collaborators or backup streaming setups without granting permanent account access.
- Prefix:
lm_share_ (9 characters)
- Body: 32 random bytes, hex-encoded (64 characters)
- Total length: 73 characters
- Transport:
?token=lm_share_… query parameter or Authorization: Bearer lm_share_… header
- Rate-limited at 600 requests/min (same tier as Popout and Overlay tokens)
has_permission(_) always returns false
- Cannot access REST or GraphQL endpoints
- Can only subscribe to the
overlay:{key} WebSocket channel matching the token's bound overlay
- Server checks expiry every heartbeat tick (5s) — disconnects with
TOKEN_EXPIRED when elapsed
- Server checks DB revocation every 30 seconds — disconnects with
TOKEN_REVOKED if revoked
Token Types
| Type | Prefix | Length | Purpose |
|---|
| System Key | lm_sys_ | 71 | Internal service-to-service auth |
| User API Key | lm_usr_ | 71 | External API access |
| JWT | lm_ + eyJ... | variable | Session-based auth |
| Popout Token | lm_pop_ | 71 | Overlay/popout access with a scoped permission subset |
| Overlay Token | lm_overlay_ | 75 | Per-overlay browser source access |
| Shared Overlay Token | lm_share_ | 73 | Temporary shared overlay link |
| Extension Token | lm_ext_ | 71 | Extension iframe, scoped to one install |
| Widget Token | lm_widget_ | 74 | Per-widget-instance browser source access |
Popout tokens accept an optional expires_at; absent or null means the token never expires. See Authentication for transports and rate limits.
Extension Permissions
Extension tokens (lm_ext_*) have no RBAC permissions (has_permission() always returns false). Instead, extensions declare required permissions in lumio.config.json and are granted only those capabilities at install time. The Lumio API validates each action call against the extension's declared permission set.
Available Extension Permissions
The manifest validator accepts exactly these strings in lumio.config.json permissions — anything else fails validation at upload:
| Permission | Description | Review level |
|---|
events:read | Read events from the Lumio event bus | Standard |
events:emit | Emit custom events | Standard |
chat:read | Read chat messages | Standard |
chat:send | Send chat messages | Standard |
chat:delete | Delete chat messages | Enhanced |
chat:ban | Ban and unban users, timeout users | Enhanced |
obs:set_scene | Switch the active OBS scene | Standard |
obs:set_source_visible | Toggle OBS source visibility | Standard |
obs:start_stream | Start the OBS stream | Standard |
obs:stop_stream | Stop the OBS stream | Standard |
overlay:update_layer | Update overlay layer properties | Standard |
Enhanced review: Extensions requesting chat:ban or chat:delete receive additional scrutiny during the approval process. Reviewers verify that the extension implements appropriate safeguards (e.g., confirmation prompts, configurable thresholds, exempt role support) and that the moderation logic does not create excessive false positives. The chat:ban permission grants ban, unban, and timeout capabilities through a single declaration.
Action-to-declaration mapping
At call time, each action resolves to the manifest permission the install must have declared; an undeclared permission is rejected before the action runs:
| Action | Required declaration |
|---|
chat:send, chat:delete | chat:read |
events:emit | events:read |
overlay:update_layer | overlays:edit |
obs:set_scene, obs:set_source_visible, obs:start_stream, obs:stop_stream | obs:control |
obs:control and overlays:edit are the strings this mapping checks for; they are not among the values the manifest validator accepts, so the OBS and overlay-layer actions cannot currently be authorised through a validated manifest.
API Keys
A User API key carries an explicit permission list stored on the key row, not a live reference to the creator's role — the scope is fixed at creation and can never exceed what the creator held then. GET /v1/tokens/me (GraphQL myPermissions) reports that effective scope verbatim, never a wildcard. Keys may carry an expires_at; an expired key resolves to Anonymous rather than erroring. Rate limits differ by auth type — see Authentication.