Skip to content

Permission API ​

Permission API is a Role-Based Access Control (RBAC) service that allows third-party applications to manage user roles and permissions. You can define multiple Roles, each containing Allow and Deny permission sets, and then assign roles to users. When a user has multiple roles, they are merged using Priority — higher-priority roles override lower-priority ones.

Overview ​

ItemDescription
Base Path/permission/api/
AuthenticationBasic (app mode) or Bearer (user mode)
Data FormatJSON
Role LimitUp to 50 roles per application
Permission LimitUp to 200 allow + deny entries combined per role

Authentication ​

Permission API supports two authentication modes via the Authorization header:

App Mode (Basic) ​

Authenticate using the application's client_id and client_secret. App mode can perform all role and user management operations.

Authorization: Basic base64(client_id:client_secret)

⚠️ Security Warning: The client_secret grants full administrative access to your application. Never embed it in client-side applications distributed to users — secrets can be extracted through reverse engineering. App mode should only be used in trusted server environments or internal development tools.

Recommended Architecture: User-facing clients should use Bearer mode (user OIDC login) combined with Cloud Functions and Permission Groups for business logic.

User Mode (Bearer) ​

Authenticate using the user's OAuth access_token. In user mode, available operations depend on the role's Visibility settings — see the "Visibility & Public Operations" section below.

Authorization: Bearer <access_token>

🔒 Access Note: User mode access depends on the role's public settings. Roles are private by default — users cannot access them via API directly. To allow client-side calls, enable the corresponding public access toggles in the developer dashboard. It is recommended to use cloud functions for permission logic.

App mode (Basic auth) is not restricted by visibility and can always perform all operations.

Replay Protection ​

All API requests must include the following headers to prevent replay attacks:

HeaderDescription
X-TimestampCurrent Unix timestamp (seconds), must be within ±5 minutes of server time
X-NonceRandom unique string, 16-64 alphanumeric characters, different for each request

Core Concepts ​

Roles ​

A role is a collection of permissions with the following properties:

PropertyTypeDescription
namestringRole name, unique within the application
descriptionstringRole description
allowstring[]List of allowed permission identifiers
denystring[]List of denied permission identifiers
prioritynumberPriority level; higher values mean higher priority, used for conflict resolution during merge
visibilitynumberVisibility bitmask (see below)
publicMemberOpstringPublic member operation scope: add / remove / all
publicPermOpstringPublic permission operation scope: add / remove / all

Permissions ​

Permission identifiers are custom strings defined by your application (recommended format: resource.action, e.g., user.read, post.write).

Allow / Deny Model ​

Each role can define both allow and deny permissions. When a user has multiple roles, the system uses a priority-based merge algorithm to compute the final permissions:

  1. Sort all the user's roles by priority from highest to lowest
  2. For each permission identifier, the first role to declare it (whether in allow or deny) has the final say
  3. Lower-priority declarations of the same permission are ignored

Example:

Role A (priority: 10)
  allow: ["read", "write"]
  deny:  ["delete"]

Role B (priority: 5)
  allow: ["delete", "admin"]
  deny:  ["write"]

Merged result:

  • read → allowed (Role A allow, priority 10)
  • write → allowed (Role A allow, priority 10; Role B deny ignored)
  • delete → denied (Role A deny, priority 10; Role B allow ignored)
  • admin → allowed (only declared by Role B, priority 5)

Permission check logic: Check merged deny list first (hit → denied), then allow list (hit → allowed), neither → denied.

Visibility & Public Operations ​

Roles are private by default — only app mode (Basic) can operate on them. By setting the visibility bitmask, you can progressively expose operations to user mode (Bearer):

Public Permission Check Toggle ​

In addition to role-level visibility controls, there is an application-level toggle controlling access to permission query APIs:

ToggleDescription
Public Permission CheckControls whether user mode (Bearer) can call "Get Merged Permissions" and "Check Permission" endpoints
  • Disabled (default): User mode cannot call permission query endpoints. Use cloud function $permission.checkPermission() / $permission.getUserPermissions() instead.
  • Enabled: User mode can directly call GET /users/{uid}/permissions and GET /users/{uid}/check.

💡 Recommended: Use cloud functions for permission check logic. Only enable this toggle when client-side direct queries are truly needed.

Role Visibility Bitmask ​

BitValueMeaning
VIS_PUBLIC1Public role: user mode can view (list, get details)
VIS_EDIT_MEMBER2Public member editing: user mode can assign/remove users (limited by publicMemberOp)
VIS_EDIT_PERM4Public permission editing: user mode can modify role's allow/deny (limited by publicPermOp)

visibility is a combination of bitmask values:

ValueMeaning
0Fully private (default)
1Public read-only
3Public + member editing
7Public + member editing + permission editing

publicMemberOp and publicPermOp further refine the scope of public operations:

ValueMeaning
addAllow add operations only
removeAllow remove operations only
allAllow both add and remove operations

Role Management ​

Roles are created and managed through the developer management panel. To dynamically control role assignments and permissions at runtime, use the $permission bridge object in Cloud Functions.

API Endpoints ​

List All Roles ​

http
GET /permission/api/roles

In user mode, only roles with visibility including VIS_PUBLIC are returned.

Response:

json
{
  "status": "ok",
  "roles": [
    {
      "name": "admin",
      "description": "Administrator",
      "allow": ["user.read", "user.write", "post.manage"],
      "deny": [],
      "priority": 100,
      "visibility": 0,
      "publicMemberOp": "all",
      "publicPermOp": "all"
    },
    {
      "name": "member",
      "description": "Regular member",
      "allow": ["user.read", "post.read"],
      "deny": ["post.delete"],
      "priority": 0,
      "visibility": 3,
      "publicMemberOp": "add",
      "publicPermOp": "all"
    }
  ]
}

Get Role Details ​

http
GET /permission/api/roles/{roleName}

In user mode, the role must be public (visibility & 1).

Path Parameters:

ParameterTypeDescription
roleNamestringRole name

Response:

json
{
  "status": "ok",
  "role": {
    "name": "admin",
    "description": "Administrator",
    "allow": ["user.read", "user.write", "post.manage"],
    "deny": [],
    "priority": 100,
    "visibility": 0,
    "publicMemberOp": "all",
    "publicPermOp": "all"
  }
}

List Role Members ​

http
GET /permission/api/roles/{roleName}/members?page=1&limit=100

In user mode, the role must be public (visibility & 1). App mode returns email, activation state, and assignment metadata. User mode only returns uid, rid, and assignment time to avoid exposing management fields.

Path Parameters:

ParameterTypeDescription
roleNamestringRole name

Query Parameters:

ParameterTypeDescription
pagenumberPage number, default 1
limitnumberItems per page, default 100, maximum 200

Response:

json
{
  "status": "ok",
  "roleName": "member",
  "total": 2,
  "page": 1,
  "limit": 100,
  "members": [
    {
      "uid": "94f48446-57ab-11f1-aa60-00163e15156d",
      "rid": "SmallMoonSet",
      "email": "user@example.com",
      "authState": 1,
      "assignedByUid": null,
      "assignedByRid": null,
      "assignedAt": "1780219728000"
    }
  ]
}

Get User's Roles ​

http
GET /permission/api/users/{uid}/roles

In user mode, only public roles are returned.

Path Parameters:

ParameterTypeDescription
uidstringUser uid

Response:

json
{
  "status": "ok",
  "roles": [
    {
      "name": "member",
      "description": "Regular member",
      "allow": ["user.read", "post.read"],
      "deny": ["post.delete"],
      "priority": 0,
      "visibility": 3,
      "assignedBy": "app",
      "assignedAt": "2025-01-15T08:30:00.000Z"
    }
  ]
}

In user mode, the response additionally includes visibility, assignedBy (assignment source), and assignedAt (assignment time) fields.

Get User's Merged Permissions ​

Returns the final allow and deny lists after priority-based merging of all the user's roles.

🔒 Access Control: Calling this endpoint in user mode (Bearer) requires the "Public Permission Check" toggle to be enabled. When disabled, use cloud function $permission.getUserPermissions(uid) instead.

http
GET /permission/api/users/{uid}/permissions

Path Parameters:

ParameterTypeDescription
uidstringUser uid

Response:

json
{
  "status": "ok",
  "allow": ["user.read", "user.write", "post.manage", "post.read"],
  "deny": ["post.delete"]
}

Check User Permission ​

Check logic: merged deny hit → false, allow hit → true, neither → false.

🔒 Access Control: Calling this endpoint in user mode (Bearer) requires the "Public Permission Check" toggle to be enabled. When disabled, use cloud function $permission.checkPermission(uid, permission) instead.

http
GET /permission/api/users/{uid}/check?permission={permission}

Path Parameters:

ParameterTypeDescription
uidstringUser uid

Query Parameters:

ParameterTypeDescription
permissionstringPermission identifier to check

Response:

json
{
  "status": "ok",
  "hasPermission": true
}

Assign Role to User ​

In user mode, the role must have visibility & VIS_EDIT_MEMBER, and publicMemberOp must be add or all.

http
POST /permission/api/users/{uid}/roles
Content-Type: application/json

Path Parameters:

ParameterTypeDescription
uidstringUser uid

Request Body:

json
{
  "roleName": "member"
}

Response:

json
{
  "status": "ok"
}

Remove User Role ​

In user mode, the role must have visibility & VIS_EDIT_MEMBER, and publicMemberOp must be remove or all.

http
DELETE /permission/api/users/{uid}/roles/{roleName}

Path Parameters:

ParameterTypeDescription
uidstringUser uid
roleNamestringRole name

Response:

json
{
  "status": "ok"
}

Update Role Permissions ​

In user mode, the role must have visibility & VIS_EDIT_PERM, and publicPermOp controls the allowed operations (adding/removing permission entries).

http
PUT /permission/api/roles/{roleName}/permissions
Content-Type: application/json

Path Parameters:

ParameterTypeDescription
roleNamestringRole name

Request Body:

json
{
  "allow": ["user.read", "post.read", "post.write"],
  "deny": ["post.delete"]
}

Response:

json
{
  "status": "ok"
}

Error Responses ​

All error responses follow a unified format:

json
{
  "error": "error_code",
  "error_description": "Human-readable error message"
}

Common Error Codes:

Error CodeHTTP StatusDescription
unauthorized401Missing Authorization header
invalid_client401Invalid Basic credentials
invalid_token401Invalid Bearer token
forbidden403Role not visible or operation not allowed in user mode
service_not_enabled403Permission service not enabled for this application
not_found404Role not found or user does not have this role
duplicate400User already has this role
limit_exceeded400allow + deny combined exceeds 200 entry limit

Usage Examples ​

Typical Game Permission Design ​

Role: admin (priority: 100, visibility: 0)
  allow: player.ban, player.kick, inventory.manage, chat.moderate
  deny:  (none)

Role: moderator (priority: 50, visibility: 1)
  allow: player.kick, chat.moderate
  deny:  player.ban

Role: muted (priority: 80, visibility: 3, publicMemberOp: add)
  allow: (none)
  deny:  chat.send, chat.color

Role: vip (priority: 10, visibility: 1)
  allow: chat.color, inventory.extra_slots
  deny:  (none)

If a user has both muted (priority 80) and vip (priority 10) roles:

  • chat.color → denied (muted deny priority 80 > vip allow priority 10)
  • inventory.extra_slots → allowed (only declared by vip)
  • chat.send → denied (muted deny)

Server-Side Permission Check ​

javascript
// Check if a player has ban permission
const res = await fetch(
  `${API_BASE}/permission/api/users/${playerUid}/check?permission=player.ban`,
  { headers: { Authorization: `Basic ${btoa(`${clientId}:${clientSecret}`)}` } }
)
const { hasPermission } = await res.json()
if (!hasPermission) {
  throw new Error('You do not have permission to ban players')
}

Using $permission in Cloud Functions ​

javascript
// Use the $permission bridge object inside cloud functions
const roles = await $permission.getUserRoles(uid)
const perms = await $permission.getUserPermissions(uid)
const canBan = await $permission.checkPermission(uid, 'player.ban')

// Update role permissions (requires app-level access to the role)
await $permission.updateRolePermissions('moderator', {
  allow: ['player.kick', 'chat.moderate', 'chat.pin'],
  deny: ['player.ban']
})

© 2026 StarEcho