Roles, permissions and inheritance
How Discord roles become permissions, how inheritance reaches other servers, how to check them, and how fresh an answer is.
MCDI turns Discord roles into permissions your project can check. You ask "may this member do X on this server" and get a yes or no, without reading Discord yourself. This page explains where a permission comes from, how inheritance extends it across servers, and how fresh an answer is.
Where a permission comes from
An admin attaches permissions to roles in the admin panel. A permission is just a name in capitals such as MANAGE_EVENTS. A member holds a permission when any of four sources gives it to them:
| Source | The member gets it when |
|---|---|
global | They hold a role that is marked global and has the permission. It counts on every server. |
server | They hold a role on that server that has the permission. |
hierarchy | They hold a role on that server with a rank at least as high as a role that has the permission. Ranks are numbers where a lower number is a higher rank, for example Executive 1, Lead 2, Member 3. A member whose best role is rank 2 gets the permissions of the rank 3 roles. |
inherited | A role they hold on the main server has an inheritance rule that reaches this server. See below. |
The special permission ADMINISTRATOR from any source grants every permission.
Check one permission
curl -s -X POST "$MCDI_URL/permissions/check" \
-H "X-API-Key: $MCDI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"discordId": "800000000000000010",
"serverId": "900000000000000001",
"permission": "send_announcements"
}'
{ "allowed": true, "source": "server" }
The permission name is not case sensitive. source says where the answer came from: global, server, hierarchy, inherited, or none when the member does not hold it. Once MCDI has the member's permissions cached, source reads cached instead, whatever the origin, so do not build logic on it. An unknown permission name is not an error, it is { "allowed": false }.
Check several at once
mode is required and is ALL or ANY:
curl -s -X POST "$MCDI_URL/permissions/check-batch" \
-H "X-API-Key: $MCDI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"discordId": "800000000000000010",
"serverId": "900000000000000001",
"permissions": ["SEND_ANNOUNCEMENTS", "MANAGE_EVENTS"],
"mode": "ALL"
}'
With ALL the answer lists what is missing, with ANY what matched:
{ "allowed": false, "missing": ["MANAGE_EVENTS"] }
{ "allowed": true, "matched": ["SEND_ANNOUNCEMENTS"] }
Get everything a member can do
One call returns the full resolved set and where each permission came from:
curl -s "$MCDI_URL/permissions/$MCDI_SERVER_ID/800000000000000010" \
-H "X-API-Key: $MCDI_API_KEY"
{
"discordId": "800000000000000010",
"serverId": "900000000000000001",
"permissions": ["SEND_ANNOUNCEMENTS", "VIEW_ROOMS"],
"sources": {
"global": [],
"server": ["SEND_ANNOUNCEMENTS"],
"hierarchy": ["VIEW_ROOMS"],
"inherited": []
}
}
This is the cheapest way to show or gate several things on one page: one call, then check the list in your own code.
Calling these endpoints needs a valid key. Read API keys and server access for a caveat about which servers a key may ask about.
Role inheritance across servers
MicroClub has one main server and others, such as a club's own. Inheritance lets a role earned on the main server count on the others.
An admin creates an inheritance rule for a role of the main server and chooses where it reaches: every other server, or a selected list. The rule can be turned off. The rule works by name. If a member holds the role Organizer on the main server and the rule reaches server B, the member gets every permission of any role on server B named organizer (names are compared without regard to case or surrounding spaces). They do not need to be a member of server B, and the source is inherited.
Three consequences are worth knowing:
- A rule belongs to a single source role, and a role has at most one rule.
- Inheritance only goes from the main server outwards. On the main server itself nothing is inherited.
- Nothing is copied. The permissions stay on the roles of server B, so removing a permission from that role removes it for everyone who had it by inheritance.
The same rule decides who reads inbound webhook submissions
An inbound webhook can opt in to role inheritance with allowRoleInheritance. A member then also reads its submissions when a role they hold on the main server inherits one of the granted reader roles, by the same rule. See Reading submissions.
How fresh is an answer
MCDI caches each member's resolved permissions per server for 5 minutes. An admin can change that time in the admin settings.
| What changes | Effect on answers |
|---|---|
| A member gains or loses a role in Discord | MCDI's sync clears that member's cache, so the next check is current. |
| A role is added, changed or removed | The sync clears the cache of that server. |
| An admin adds or removes a permission on a role in the admin panel | The cache of that server is cleared. |
| An admin creates or edits an inheritance rule | Nothing is cleared. Answers follow the new rule once the cached entries expire, within the cache time. |
Choosing who may sign in to your project
A project can limit its login to certain roles. When an admin picks roles for the project, a member must hold at least one of them to sign in, and anyone else is sent back with insufficient_roles (Login with MicroClub lists the errors). A project with no roles accepts every member of the server.
Source: apps/api/src/modules/permissions/permissions.service.ts, apps/api/src/modules/permissions/permissions.repository.ts, apps/api/src/modules/permissions/permission-cache.service.ts, apps/api/src/modules/inbound-webhooks/inbound-webhooks.repository.ts, apps/api/src/modules/auth/auth.service.ts.