Access Control
Table of Contents
- Permission Model
- Permission Namespaces
- Filters
- Enforcement Tiers
- Roles
- HTTP Resources
- User Role Management
Permission Model
Access control is facilitated through roles. Each role contains multiple permissions. The permission model has two clean namespaces: route permissions (endpoint access) and model permissions (field-level data access with optional document filters).
For detailed documentation on how complex permissions interact — deny rules with filters, wildcard + deny combinations, evaluation order, and enforcement mechanics — see Permission Evaluation.
Each permission has:
path— the resource pathaction— the permitted actionallow— whether to allow or denyfilter— (optional) a MongoDB query that scopes access to specific documents
Permission Namespaces
Route Permissions
Route permissions gate access to HTTP endpoints and socket events. Paths are prefixed with /routes/.
Actions: get, post, put, delete, *
{"path": "/routes/bots", "action": "get", "allow": true}
This grants access to /routes/bots only. To also cover subpaths like /routes/bots/123, use a trailing wildcard:
{"path": "/routes/bots/*", "action": "get", "allow": true}
Multiple permissions accumulate:
[
{"path": "/routes/bots/*", "action": "get", "allow": true},
{"path": "/routes/bots/*", "action": "post", "allow": true}
]
Deny permissions override allows for matching paths:
[
{"path": "/routes/bots/*", "action": "*", "allow": true},
{"path": "/routes/bots/21312", "action": "*", "allow": false}
]
A * in a non-trailing position matches any single path segment:
{"path": "/routes/users/*/properties", "action": "get", "allow": true}
A trailing * matches the parent path and all deeper subpaths:
{"path": "/routes/bots/*", "action": "get", "allow": true}
Model Permissions
Model permissions gate data access at the field level. Paths follow the format /models/<model>/<field|*>.
Actions: read, write, delete, *
Grant read access to all fields on users:
{"path": "/models/users/*", "action": "read", "allow": true}
Grant write access to specific fields only:
[
{"path": "/models/users/username", "action": "write", "allow": true},
{"path": "/models/users/email", "action": "write", "allow": true}
]
Grant delete access on a model (field segment is * for delete):
{"path": "/models/users/*", "action": "delete", "allow": true}
The delete action means "can delete documents from this model." Methods like find, create, etc. are implementation details — permissions gate the data, not the mechanism.
Capability Permissions
Capability permissions control sandbox features for runnables (scripts/jobs).
{"path": "/capabilities/network", "action": "read", "allow": true}
{"path": "/capabilities/require/lodash", "action": "read", "allow": true}
Role Assignment Permissions
Controls which roles a user or runnable can assign to others:
{"path": "/roles/ROLE_ID/assign", "action": "write", "allow": true}
Filters
The optional filter field on a permission is a MongoDB query object that scopes which documents the permission applies to.
Filter Examples
User can only read/write their own user document:
{"path": "/models/users/*", "action": "*", "allow": true, "filter": {"_id": "auth_id"}}
User can only read/write their own properties:
{"path": "/models/user_properties/*", "action": "*", "allow": true, "filter": {"parent_id": "auth_id"}}
Runnable can only read bots tagged 'npc':
{"path": "/models/bots/*", "action": "read", "allow": true, "filter": {"tags": "npc"}}
Filter Enforcement
- Reads: Filters are ANDed into the query. Documents outside the filter are invisible.
- Writes: For updates, the filter is ANDed into the query so only matching documents are modified.
- Deletes: The filter is ANDed into the delete query so only matching documents can be deleted.
- Multiple permissions with different filters: Union semantics — filters are ORed together.
- Permissions without filters: Apply to all documents (no restriction).
auth_id Substitution
The special token auth_id in permission paths and filter values is replaced at evaluation time. For HTTP requests, it is replaced with the authenticated user's ID. For runnables (scripts/jobs), it is replaced with the triggering entity's ID — the user, bot, or parent that caused the script to fire.
This allows a single role definition to scope access to the relevant entity's own resources:
{
"path": "/routes/users/auth_id/*",
"action": "*",
"allow": true
}
When user abc123 authenticates, this becomes /routes/users/abc123/*, granting access to all subpaths under the user's own resource.
The same substitution works recursively in filter objects:
{"filter": {"_id": "auth_id"}}
Becomes {"_id": "abc123"} for user abc123.
Runnable entity mapping
| Script type | auth_id resolves to |
|---|---|
| Property change | parent._id (the user, bot, or global) |
| Message event | subject._id (the user or bot receiving/sending) |
| Signal event | parent._id |
| User lifecycle | user._id |
| Bot lifecycle | bot._id |
| Socket connection | user._id |
| Socket event | user._id |
| HTTP request | user._id (only present when requires_user) |
| Job | Not substituted — jobs have no entity context |
Enforcement Tiers
CRUD Routes
Routes that go through the standard CRUD handler get full field-level enforcement:
- Route permission gates endpoint access
- Reads filter to permitted fields and inject document filters
- Writes validate all fields have write permission
- Deletes check delete permission and inject document filters
Custom Route Handlers
Handlers like login, register, verify use the raw model directly for unrestricted internal access. Field permissions only filter the final response sent to the client via getSafe.
Runnables (Scripts/Jobs)
- No route permissions — runnables bypass HTTP routes
- Full field-level + filter enforcement on all model operations
- Scripts see
undefinedfor restricted fields and don't see documents outside their filters auth_idsubstitution uses the triggering entity's ID (see auth_id Substitution)
Roles
A role groups permissions together. Changes to a role apply to all role holders.
Role Scopes
anonymous— applied to all non-authenticated requests.user-default— resolved dynamically for all authenticated users. An authenticated user's effective permissions are the union of:anonymouspermissions +user-defaultpermissions + any roles explicitly assigned to the user. Becauseuser-defaultis resolved at evaluation time (not copied at registration), changes to theuser-defaultrole take immediate effect for all authenticated users.runnable-default— base permissions for scripts and jobs. Grants read access to all model fields by default.normal— a role that can be manually assigned to users or runnables for various purposes.
Default Roles
admin (scope: normal):
{"permissions": [{"path": "/*", "action": "*", "allow": true}]}
anonymous (scope: anonymous):
{
"permissions": [
{"path": "/routes/mcp/*", "action": "*", "allow": true},
{"path": "/routes/users/oauth/*", "action": "*", "allow": true},
{"path": "/routes/users/register", "action": "post", "allow": true},
{"path": "/routes/users/login", "action": "post", "allow": true},
{"path": "/routes/users/trigger_verify_notification", "action": "post", "allow": true},
{"path": "/routes/users/verify", "action": "post", "allow": true},
{"path": "/routes/users/change_password_request", "action": "post", "allow": true},
{"path": "/routes/users/change_password_verify", "action": "post", "allow": true},
{"path": "/routes/users/*/change_email_verify", "action": "post", "allow": true},
{"path": "/routes/users/*/refresh_token", "action": "post", "allow": true},
{"path": "/routes/users/generate", "action": "post", "allow": true},
{"path": "/routes/requests/*", "action": "*", "allow": true},
{"path": "/routes/requests_raw/*", "action": "*", "allow": true},
{"path": "/models/users/*", "action": "*", "allow": true}
]
}
user (scope: user-default):
{
"permissions": [
{"path": "/routes/users/auth_id/*", "action": "*", "allow": true},
{"path": "/routes/users/whoami", "action": "*", "allow": true},
{"path": "/models/users/*", "action": "*", "allow": true, "filter": {"_id": "auth_id"}}
]
}
runnable-default (scope: runnable-default):
{
"permissions": [
{"path": "/models/users/*", "action": "read", "allow": true},
{"path": "/models/bots/*", "action": "read", "allow": true},
{"path": "/models/globals/*", "action": "read", "allow": true},
{"path": "/models/conversations/*", "action": "read", "allow": true},
{"path": "/models/messages/*", "action": "read", "allow": true},
{"path": "/models/storages/*", "action": "read", "allow": true},
{"path": "/models/user_properties/*", "action": "read", "allow": true},
{"path": "/models/bot_properties/*", "action": "read", "allow": true},
{"path": "/models/global_properties/*", "action": "read", "allow": true},
{"path": "/models/scripts/*", "action": "read", "allow": true},
{"path": "/models/jobs/*", "action": "read", "allow": true}
]
}
HTTP Resources
GET /roles
Paginated list of roles:
[
{
"_id": "txy3831ff9h",
"title": "user",
"scope": "user-default",
"permissions": [
{"path": "/routes/users/auth_id/*", "action": "*", "allow": true},
{"path": "/routes/users/whoami", "action": "*", "allow": true},
{"path": "/models/users/*", "action": "*", "allow": true, "filter": {"_id": "auth_id"}}
]
},
{
"_id": "hxy1231ff9g",
"title": "admin",
"scope": "normal",
"permissions": [
{"path": "/*", "action": "*", "allow": true}
]
}
]
POST /roles
Create a role:
{
"title": "custom-reader",
"scope": "normal",
"permissions": [
{"path": "/routes/bots/*", "action": "get", "allow": true},
{"path": "/models/bots/*", "action": "read", "allow": true}
]
}
GET /roles/:id
Retrieve a single role by ID.
{
"_id": "txy3831ff9h",
"title": "user",
"scope": "user-default",
"permissions": [
{"path": "/routes/users/auth_id/*", "action": "*", "allow": true},
{"path": "/routes/users/whoami", "action": "*", "allow": true},
{"path": "/models/users/*", "action": "*", "allow": true, "filter": {"_id": "auth_id"}}
]
}
PATCH /roles/:id
Partially update a role. Accepts the same body format as PUT but only the provided fields are updated.
{
"title": "renamed-role"
}
PUT /roles/:id
Update the role with the given id.
{
"title": "updated-role",
"permissions": [
{"path": "/routes/users/auth_id/*", "action": "*", "allow": true},
{"path": "/models/users/*", "action": "*", "allow": true, "filter": {"_id": "auth_id"}}
]
}
DELETE /roles/:id
Delete the role with the given id.
User Role Management
Manage the explicit roles assigned to a user. These endpoints are on the /users route but are documented here because they are part of the access control system.
Modifying a user's roles requires two permissions:
- Model write on users —
/models/users/*withwriteaction - Role assignment for each role being added or removed —
/roles/{roleId}/assignwithwriteaction
Both checks apply to POST and DELETE operations. See Role Assignment Permissions for details.
GET /users/:id/roles
Returns the list of role IDs explicitly assigned to the user.
["txy3831ff9h", "abc1234def5"]
POST /users/:id/roles
Set the user's roles. Replaces the entire roles array.
REQUEST
{
"roles": ["txy3831ff9h", "abc1234def5"]
}
RESPONSE
["txy3831ff9h", "abc1234def5"]
DELETE /users/:id/roles/:role_id
Remove a single role from the user.
RESPONSE
["txy3831ff9h"]