ErrLookup › Background articles › "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries
"You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries
"Not allowed", 403 Forbidden, and "you do not have permission" errors mean the request was authenticated but the caller's role, scope, or permissions did not cover the action. This guide explains why servers reject authorized callers, the common causes — missing role grants, scoped keys acting on more-privileged objects, admin-only endpoints hit with user tokens, and anti-escalation guards — and how to fix them.
Distilled from 111 documented records across 31 repositories.
Background
This family sits at the authorization layer: the server has already identified the caller (a valid API key, session, OAuth token, or DDP/WebSocket session) and is now refusing the action itself. Unlike a 401, retrying with the same credentials will not help — the fix is a different credential, a changed role, or a narrower request. Libraries surface it in many shapes: HTTP 403 (tailscale's localapi, litellm's guards, InvokeAI board moves, Nextcloud trashbin), HTTP 400 with an explanatory message (litellm admin-view gates, Immich key rotation), Meteor.Error codes like 'error-not-allowed' or 'error-board-notAdmin' (Rocket.Chat, Wekan), JSON-RPC AuthorizationException -32001 (Leantime), or backend error codes like NotPermitted/PrivilegesRequired that frontend interceptors translate into redirects (pentagi).
Why the check exists varies, but the records cluster around a few rationales. Privilege separation: litellm reserves fields like estimated_output_tokens and allowed_passthrough_routes for proxy admins because letting lower roles set them would undermine limits or let an entity escalate itself. Tenant isolation: litellm's analytics guard blocks service-account keys (which have no user_id) from self-scoped analytics because a null entity id would mean 'no filter' and leak every tenant's data. Anti-escalation: Immich refuses to rotate a key whose permissions exceed the calling key's — otherwise a narrow key could learn the secret of a broader one; Appwrite rejects upserts that grant permission roles the caller does not hold, and restricts bulk transaction actions to API keys and admins because bulk operations skip per-record permission checks. Ownership: Navidrome, InvokeAI, and Nextcloud gate mutations on owning the specific object — you can see a shared playlist, calendar, or board but not modify it.
From the caller's side the error is often confusing because the object clearly exists and the credentials are valid. Several libraries deliberately blur the line between 403 and 404: Navidrome returns 403 only when the playlist is visible to you (invisible ones get 404), while litellm embeds the caller's resolved role and user_id in the error detail so you can tell 'wrong key on the header' from 'genuinely missing grant'. A recurring trap is that the permission checked is not the one you assumed: Rocket.Chat's incoming-integration check tests the post-as user's message-impersonate permission, not the caller's; Immich's rotate check applies only to API-key auth (session calls pass); Leantime's task-sort gate uses the global session role while a later check uses per-project assignment; Wekan reads the isAdmin flag server-side regardless of what the client UI showed.
Across libraries, the shape of the permission model explains most variance: global role ladders (Leantime editor+, Wekan isAdmin), per-room or per-object grants (Rocket.Chat room permissions, Navidrome playlist ownership, Nextcloud shared_access READ_WRITE vs READ), key/scope systems (claude-mem's memories:admin scope, Immich permission subsets, Appwrite documents.write scope), and object-type gates (smart/locked playlists, non-federated rooms, lifecycle integration events). The common thread: the server models some privilege above authentication, and this error is that model saying no.
Common causes
- Calling an admin-only endpoint or field with a non-admin credential. The most frequent pattern: budget info, key block/unblock, SMTP test email, board archiving, OAuth app creation, or privileged fields like allowed_passthrough_routes are restricted to admin roles, and a regular user, team key, or bot token is used instead. The credential is valid — it simply resolves to a role below the required one.
- Scoped key or token lacking a permission the action requires. API keys and bot roles carry explicit permission sets, and the endpoint needs one the key was never granted: a Rocket.Chat bot without create-c or set-owner, an Immich key whose permissions are a strict subset of the target key, a claude-mem key without memories:admin, an Appwrite call missing documents.write. Frequently the key was provisioned for one workflow and reused for another.
- Anti-escalation and privilege-boundary guards rejecting the operation. Some 403s fire precisely because granting the request would let the caller grow its own power: litellm blocks non-admins setting rate-limiter reservation fields or passthrough routes, Immich blocks rotating a more-privileged key with a narrower one, Appwrite forbids granting roles you do not hold and bulk transaction actions from session users. These are by design and should not be retried.
- Not the owner of the object being mutated. You can see the object but not change it: a playlist owned by another user (Navidrome), a video on someone else's board (InvokeAI requires direct ownership even on public boards), a calendar shared read-only (Nextcloud gates trashbin restore and permanent delete on shared_access being READ_WRITE). Visibility is not write access.
- Missing membership plus permission combinations. Several checks require both a relationship and a grant: Rocket.Chat's invitation path needs membership plus add-user-to-joined-room (or the type-specific any-c/any-p permission); Leantime requires manager+ role AND assignment to every project in the batch; channels.online needs actual room access, not just a valid token.
- Stale session, role change, or wrong key on the header. The server's view of your role diverges from what you think: a session cached before a role downgrade (Leantime reads the session role), a promoted Wekan admin whose flag the old session has not refreshed, or simply a different — lower-privilege — key sitting on the Authorization header than the one you meant to use. Litellm echoes user_role/user_id in the error for exactly this diagnosis.
- The permission checked is on a different principal than you assumed. Some guards evaluate someone else's permissions: Rocket.Chat's incoming integration checks the post-as user's message-impersonate permission, not the creator's; Rocket.Chat's addRoomOwner uses the fromUserId argument, not the logged-in caller. The fix is to fix that principal's roles, not your own.
- Client UI issuing calls the server will refuse. Client-side hiding of privileged actions is cosmetic — Wekan's SMTP test, Leantime's drag-drop sorting, neko's clipboard UI can all be invoked by clients whose profile lacks the permission, and the server rejects them. Automation scripts frequently make the same mistake the hidden button would have.
What usually fixes it
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
Go deeper
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
Documented occurrences
- Only proxy admins can set {ESTIMATED_OUTPUT_TOKENS_FIELD} or {ESTIMATED_OUTPUT_TOKENS_PER_MODEL_FIELD} on a {entity}. They decide how many output tokens the rate limiter reserves for a request that omits max_tokens. (BerriAI/litellm)
- Forbidden (navidrome/navidrome)
- Cannot rotate an API Key with permissions you do not have (immich-app/immich)
- Only proxy admins can set `allowed_passthrough_routes` on a {entity}. (BerriAI/litellm)
- Admin-only endpoint. Not allowed to access this., your role={user_api_key_dict.user_role} (BerriAI/litellm)
- Service-account keys cannot query user analytics. Use a user-bound key, or call as a proxy admin. (BerriAI/litellm)
- -32001: You are not allowed to re-sort one or more of these projects. (Leantime/leantime)
- -32001: You do not have access to this project's tags. (Leantime/leantime)
- user_unauthorized: The current user is not authorized to perform the requested action. (appwrite/appwrite)
- error-not-allowed: Not allowed (RocketChat/Rocket.Chat)
- -32001: You are not allowed to re-sort tasks. (Leantime/leantime)
- Read-only sharees cannot permanently delete trashbin entries (nextcloud/server)
- error-not-allowed: Not allowed (RocketChat/Rocket.Chat)
- error-invalid-channel: Invalid Channel (RocketChat/Rocket.Chat)
- debug-log access denied (tailscale/tailscale)
- FORBIDDEN: You do not have permission to ${action} this package. ${errorBody} (pnpm/pnpm)
- user_unauthorized: Permissions must be one of: ({roles}) (appwrite/appwrite)
- Only proxy admins, team admins, or org admins can call {route}. user_role={user_api_key_dict.user_role}, user_id={user_api_key_dict.user_id} (BerriAI/litellm)
- Forbidden: `include=payload` requires admin scope (thedotmack/claude-mem)
- Read-only sharees cannot restore trashbin entries (nextcloud/server)
…and 91 more across the corpus — use search.
Honest provenance: generated on 2026-09-01 from AI-assisted analysis of the linked records. See how records are made.