Permission checks
Two methods on the top-level Junjo instance answer “is this user allowed to do X in this group?” - the hot path for any game-loop code.
import { Junjo } from "@junjo/sdk";
const junjo = new Junjo({ apiKey: process.env.JUNJO_API_KEY! });
if (await junjo.can(userId, groupId, "guild.kick")) {
await junjo.members.removeRole(groupId, targetUserId, recruitRoleId);
}The check is a single GET request to /v1/permissions/check. The server caches each (game, group, user, permission) answer for 60 seconds and invalidates on relevant mutations, so repeated calls inside the cache window are effectively free.
can(userId, groupId, permission)
Returns Promise<boolean> - the allowed field of the underlying PermissionCheckResult. Use this when you only need the yes/no.
if (!(await junjo.can(userId, groupId, "guild.invite_member"))) {
throw new Error("not allowed");
}Errors
| Code | Status | When |
|---|---|---|
bad_request | 400 | Empty userId, groupId, or permission, or permission over 128 characters. |
not_found | 404 | Group missing, soft-deleted, or owned by a different game. |
invalid_api_key | 401 | API key missing, malformed, or revoked. |
check(userId, groupId, permission)
Returns Promise<PermissionCheckResult> - the richer answer that includes why a check passed or failed. Use this when you want to render “you can’t do this because your role X is missing key Y” UX, or when an admin tool needs to display the resolution path.
const result = await junjo.check(userId, groupId, "guild.kick");
if (!result.allowed) {
if (result.source === "override") {
showWarning("Your kick permission was explicitly revoked by an admin.");
} else {
showWarning("Your role does not allow kicking members.");
}
}Result shape
| Field | Type | Notes |
|---|---|---|
allowed | boolean | True if the user has the permission. |
source | "role" | "override" | "default" | "none" | See the source taxonomy below. |
viaRoleId | RoleId (optional) | Present only when source === "role"; the highest-priority role that granted the permission. |
Source taxonomy
| Source | When |
|---|---|
none | The user is not a member of the group, has no ExternalIdentity for this game, or is a non-active member (left, kicked, invited). allowed = false. |
default | The user is an active member with no override and no role granting this permission. allowed = false. |
role | At least one role grants the permission. allowed = true; viaRoleId is set to the highest-priority granting role. |
override | A MemberPermissionOverride row exists. allowed mirrors the override’s grant value. Override beats role. |
Errors
Same as can().
Caching behavior
Each unique (userId, groupId, permission) answer is cached server-side for 60 seconds. The following mutations invalidate the cache for the affected group:
members.assignRole,members.removeRoleroles.grantPermission,roles.revokePermissionmembers.overridePermission,members.clearPermissionOverrideroles.delete
A stale cached answer can therefore persist for at most the TTL window if a row is mutated outside the API (direct SQL, for instance). Prefer the API for any change that needs to surface immediately.
The cache is in-memory and per-server-process; it is not a Redis dependency. A horizontally-scaled cluster gets eventual consistency across instances bounded by the TTL.
See also
GET /v1/permissions/check- the underlying HTTP route.