Skip to content

Admin endpoints

Endpoints under /api/admin/... (or /api/... for resource reads without the admin/ prefix). The realm is resolved via the Host header.

Every endpoint is gated through .RequiresPermission("<resource>:<action>"). The strings are exactly the same as in the frontend sidebar.

Users

Endpoint definitions in Modgud.Api/Features/Users/UsersEndpoints.cs.

MethodPathPermission
GET/api/usersuser:read
GET/api/users/{id}user:read
POST/api/usersuser:write
PATCH/api/users/{id}user:write
DELETE/api/users/{id}user:delete
POST/api/users/{id}/unlockuser:write

Admin GDPR

MethodPathPermission
POST/api/admin/users/{id}/gdpr/delete-requestuser:delete
POST/api/admin/users/{id}/gdpr/delete-confirmuser:delete
DELETE/api/admin/users/{id}/gdpr/delete-canceluser:delete

Admin sessions

MethodPathPermission
GET/api/admin/users/{id}/sessionsuser:read
DELETE/api/admin/users/{id}/sessionsuser:write (force logout)
MethodPathPermission
POST/api/admin/users/{id}/magic-linkuser:write

Admin 2FA grace period

MethodPathPermission
GET/api/admin/users/{id}/graceuser:read
PATCH/api/admin/users/{id}/graceuser:write

Admin profile change requests

MethodPathPermission
GET/api/admin/change-requestsuser:read
POST/api/admin/change-requests/{id}/approveuser:write
POST/api/admin/change-requests/{id}/rejectuser:write

Roles

MethodPathPermission
GET/api/rolespermission-role:read
GET/api/roles/{id}permission-role:read
POST/api/rolespermission-role:write
PATCH/api/roles/{id}permission-role:write
DELETE/api/roles/{id}permission-role:delete

Groups

MethodPathPermission
GET/api/groupsauthorization-group:read
GET/api/groups/{id}authorization-group:read
POST/api/groupsauthorization-group:write
PATCH/api/groups/{id}authorization-group:write
DELETE/api/groups/{id}authorization-group:delete

Principals (polymorphic read API)

Returns users, groups, and service accounts mixed — used by search and the member picker in the frontend.

MethodPathPermission
GET/api/principals?search=...user:read (for persons) and/or authorization-group:read

OAuth clients

MethodPathPermission
GET/api/admin/oauth/clientsoauth-client:read
GET/api/admin/oauth/clients/{id}oauth-client:read
POST/api/admin/oauth/clientsoauth-client:write
PATCH/api/admin/oauth/clients/{id}oauth-client:write
DELETE/api/admin/oauth/clients/{id}oauth-client:delete
POST/api/admin/oauth/clients/{id}/rotate-secretoauth-client:write

OAuth scopes

MethodPathPermission
GET/api/admin/oauth/scopesoauth-scope:read
GET/api/admin/oauth/scopes/{id}oauth-scope:read
POST/api/admin/oauth/scopesoauth-scope:write
PATCH/api/admin/oauth/scopes/{id}oauth-scope:write
DELETE/api/admin/oauth/scopes/{id}oauth-scope:delete

OAuth APIs

MethodPathPermission
GET/api/admin/oauth/apisoauth-api:read
GET/api/admin/oauth/apis/{id}oauth-api:read
POST/api/admin/oauth/apisoauth-api:write
PATCH/api/admin/oauth/apis/{id}oauth-api:write
DELETE/api/admin/oauth/apis/{id}oauth-api:delete

Login providers

The single endpoint group for both built-in (Internal) and external (Oidc / Saml / Ldap / Kerberos) login providers. The Internal entry is auto-seeded once per realm and rejects edits / deletes — clients identify it by IsBuiltIn=true on the DTO.

MethodPathPermission
GET/api/admin/login-providerslogin-provider:read
GET/api/admin/login-providers/{id}login-provider:read
GET/api/admin/login-providers/flavorslogin-provider:read
POST/api/admin/login-providerslogin-provider:write
PUT/api/admin/login-providers/{id}login-provider:write
DELETE/api/admin/login-providers/{id}login-provider:write
POST/api/admin/login-providers/{id}/enablelogin-provider:write
POST/api/admin/login-providers/{id}/disablelogin-provider:write
POST/api/admin/login-providers/{id}/secretlogin-provider:write
POST/api/admin/login-providers/{id}/test-user-updatelogin-provider:read
GET/api/admin/login-providers/{id}/last-raw-claimslogin-provider:read

Realms

Only available on the Control-Plane realm (the realm flagged IsControlPlane = true). Otherwise 404 — the existence of realm CRUD is hidden from tenant realms. Permissions live under the control-plane app slug (realm:read|write), not under modgud. See Realm API for the request/response shapes and the InitialAdmin requirement on POST /api/admin/realms.

Auth log

MethodPathPermission
GET/api/admin/auth-log?from=...&to=...auth-log:read

App settings

MethodPathPermission
GET/api/admin/app-settingsrealm:admin
PATCH/api/admin/app-settingsrealm:admin

Projection endpoints (maintenance)

MethodPathPermission
GET/api/admin/projectionsrealm:admin
POST/api/admin/projections/{name}/rebuildrealm:admin

Permission checks in detail

PermissionEndpointFilter runs after authentication. Permission strings are two-segment <resource>:<action> inside one App's catalog — the app context is implicit from the calling endpoint group (modgud for the realm-internal admin surface, control-plane for the realm-CRUD endpoints):

1. ClaimTypes.NameIdentifier → UserId
2. The calling endpoint determines the app context (modgud or
   control-plane).
3. IPermissionService.GetUserPermissionsAsync(UserId, appSlug)
   ├── BFS over the user's groups (transitive, with visited set)
   ├── filter to groups whose BoundTo contains appSlug or "*"
   ├── filter their roles to AppId == app.Id (or IsRealmAdmin = true)
   └── bypass-pre-expand realm:admin and <r>:admin
4. PermissionEvaluator.Evaluate(grants, "<resource>:<action>"):
     has realm:admin?            → ✓
     has the exact permission?   → ✓
     has <resource>:admin?       → ✓
     otherwise                   → 403

Effective permissions are computed per request from the BFS over all the user's group memberships (transitive, including nested), expanded through the assigned PermissionRoles. See Permissions & gating for the full evaluator + UserInfo-emission story.

Pagination

List endpoints support:

ParamTypeMeaning
pageint1-based
pageSizeintItems per page
searchstringFull-text search
sortBystringSort field
sortDescendingboolSort direction

Response:

json
{
  "items": [ ... ],
  "totalCount": 234,
  "page": 1,
  "pageSize": 50
}

Real-time updates

After every mutation the backend fires a SignalR event over the UIHub. The frontend (useEntityService composable) listens and automatically refreshes the affected lists — no manual polling needed.

Hub endpoint: /signalr/ui (with auth cookie + WebSocket upgrade).

Released under the Apache-2.0 License.