Access Groups
Access groups let you bundle a set of sharing-tag grants and assign them to multiple users at once. Instead of configuring 50 identical per-user grants, create one group, define its rules, and add users to it.
Overview
Access groups sit between sharing tags and users:
Sharing Tags Access Groups Users
+---------+ +---------------+ +-------+
| manga |----->| Manga Readers |---->| alice |
| 18+ |----->| allow manga |---->| bob |
+---------+ | deny 18+ | +-------+
+---------------+
Per-user overrides
still apply on top
- A group has a name, description, and a set of tag grants (allow/deny).
- Users can belong to multiple groups.
- Per-user grants in the existing
Sharing Tag Grantspanel continue to work as overrides on top of group rules. - Deny always wins across all sources (groups + per-user).
How Grants Merge
When a user belongs to one or more groups, their effective grants are the union of:
- All grants from all their groups
- Their per-user grants (from Settings > Users > Edit > Sharing Tag Grants)
The same deny-wins rule from sharing tags applies to the merged set:
| Scenario | Result |
|---|---|
| Group allows "manga", no user override | User sees manga-tagged content (whitelist mode) |
| Group allows "manga", user denies "18+" | User sees manga-tagged content, but not 18+-tagged content |
| Two groups: one allows "manga", other denies "manga" | Deny wins; manga content hidden |
| Group allows "manga", user also allows "manga" | Same as single allow (deduplicated) |
| No groups, no user grants | User sees all content (default open) |
Any allow grant from any source (group or per-user) activates whitelist mode for that user. In whitelist mode, untagged content is hidden. This is the same behavior as per-user allow grants. See the sharing tags docs for details.
Managing Access Groups
Via Web Interface
- Go to Settings > Access Groups
- Click Create Group
- Enter a name and optional description
- After creation, click the group name to open its detail page
- Add tag grants, members, and optionally OIDC mappings
Via API
Create a group:
curl -X POST http://localhost:8080/api/v1/access-groups \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Manga Readers",
"description": "Access to all manga content"
}'
List groups:
curl http://localhost:8080/api/v1/access-groups \
-H "Authorization: Bearer $ADMIN_TOKEN"
Get group details (members, grants, OIDC mappings):
curl http://localhost:8080/api/v1/access-groups/{id} \
-H "Authorization: Bearer $ADMIN_TOKEN"
Update a group:
curl -X PATCH http://localhost:8080/api/v1/access-groups/{id} \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"description": "Updated description"}'
Delete a group:
curl -X DELETE http://localhost:8080/api/v1/access-groups/{id} \
-H "Authorization: Bearer $ADMIN_TOKEN"
Deleting a group removes all its memberships and grants. Users will lose the group's grants on their next request.
Managing Group Grants
Grants define what content the group's members can see.
Add a grant:
curl -X POST http://localhost:8080/api/v1/access-groups/{id}/grants \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"sharingTagId": "tag-uuid",
"accessMode": "allow"
}'
Remove a grant:
curl -X DELETE http://localhost:8080/api/v1/access-groups/{id}/grants/{tag_id} \
-H "Authorization: Bearer $ADMIN_TOKEN"
Managing Group Members
Add members:
curl -X POST http://localhost:8080/api/v1/access-groups/{id}/members \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userIds": ["user-uuid-1", "user-uuid-2"]
}'
Remove a member:
curl -X DELETE http://localhost:8080/api/v1/access-groups/{id}/members/{user_id} \
-H "Authorization: Bearer $ADMIN_TOKEN"
List a user's groups:
curl http://localhost:8080/api/v1/users/{user_id}/access-groups \
-H "Authorization: Bearer $ADMIN_TOKEN"
OIDC Auto-Assignment
If you use OIDC authentication, you can map IdP group names to access groups. When a user logs in via OIDC, their access group memberships are automatically reconciled:
- If the user's IdP groups include a mapped group name, they are added to the corresponding access group (with
source=oidc). - If a previously mapped group is no longer in their IdP claims, the OIDC-sourced membership is removed.
- Manually assigned memberships are never touched by the OIDC sync.
Add an OIDC mapping:
curl -X POST http://localhost:8080/api/v1/access-groups/{id}/oidc-mappings \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"oidcGroupName": "library-staff"
}'
Remove an OIDC mapping:
curl -X DELETE http://localhost:8080/api/v1/access-groups/{id}/oidc-mappings/{mapping_id} \
-H "Authorization: Bearer $ADMIN_TOKEN"
OIDC group names are matched exactly as stored. If your IdP sends Library-Staff but the mapping says library-staff, it will not match. Configure the mapping to match your IdP's exact casing.
Debugging Effective Grants
The effective grants endpoint shows exactly which grants apply to a user and where each one comes from (per-user override or group name). This is the primary tool for answering "why can't this user see series X?"
Via Web Interface:
- Go to Settings > Users
- Edit a user
- Scroll to the Effective Grants panel
Via API:
curl http://localhost:8080/api/v1/users/{user_id}/effective-grants \
-H "Authorization: Bearer $ADMIN_TOKEN"
Response:
{
"userId": "user-uuid",
"grants": [
{
"sharingTagId": "tag-uuid",
"sharingTagName": "manga",
"accessMode": "allow",
"sources": [
{
"kind": "group",
"groupId": "group-uuid",
"groupName": "Manga Readers"
}
]
},
{
"sharingTagId": "tag-uuid-2",
"sharingTagName": "18+",
"accessMode": "deny",
"sources": [
{
"kind": "user",
"groupId": null,
"groupName": null
}
]
}
]
}
Each grant shows sources with kind: "user" for per-user overrides or kind: "group" with the group name for group-inherited grants. A single tag+mode combination can have multiple sources if both a user override and a group grant contribute the same rule.
Example: Family + Organization Setup
Combine access groups with per-user overrides for a multi-layered access model:
Step 1: Create tags and groups
# Tags
curl -X POST .../admin/sharing-tags -d '{"name": "manga"}'
curl -X POST .../admin/sharing-tags -d '{"name": "comics"}'
curl -X POST .../admin/sharing-tags -d '{"name": "18+"}'
# Groups
curl -X POST .../access-groups -d '{"name": "Manga Readers"}'
curl -X POST .../access-groups -d '{"name": "Comics Readers"}'
Step 2: Configure group grants
# Manga Readers: allow manga
curl -X POST .../access-groups/{manga-group}/grants \
-d '{"sharingTagId": "manga-tag-id", "accessMode": "allow"}'
# Comics Readers: allow comics
curl -X POST .../access-groups/{comics-group}/grants \
-d '{"sharingTagId": "comics-tag-id", "accessMode": "allow"}'
Step 3: Assign users
# Alice gets manga access via group
curl -X POST .../access-groups/{manga-group}/members \
-d '{"userIds": ["alice-id"]}'
# But deny 18+ content specifically for Alice (per-user override)
curl -X PUT .../users/alice-id/sharing-tags \
-d '{"sharingTagId": "18+-tag-id", "accessMode": "deny"}'
Result: Alice sees manga-tagged content, but never 18+-tagged content (deny wins).
Best Practices
- Use groups for organizational roles rather than duplicating per-user grants
- Keep per-user grants for exceptions (e.g., one user in a "Manga Readers" group who should not see 18+ content)
- Name groups by intent (e.g., "Kids", "Staff", "Manga Readers") rather than by technical configuration
- Use the effective grants endpoint to verify access before and after changes
- Map OIDC groups when your IdP already has organizational groups, so membership stays in sync automatically