immich-app/immich · error · ForbiddenException
Missing required permission
Error message
Missing required permission: ${requestedPermission} What it means
Thrown by authenticate() when an API-key-authenticated request does not carry the permission required for the requested operation. isGranted() compares the requested permission against the key's stored permission set and fails the request with 403.
Solutions
- Regenerate or edit the API key in Immich user settings, granting the required permission
- Recreate the key and update the client's x-api-key header
- Verify you are calling the intended endpoint/version for your key's scope
Example fix
// before curl -H 'x-api-key: OLD_KEY' /api/library // after // regenerate key with required permission in Immich settings curl -H 'x-api-key: NEW_KEY' /api/library
Defensive patterns
Strategy: try-catch
Validate before calling
if (!keyPermissions.includes(requiredPermission)) throw new Error('API key lacks ' + requiredPermission); Try / catch
try { await api.request(...) } catch (e) { if (e.status === 403 && /Missing required permission/.test(e.message)) { /* prompt to grant key permission */ } throw e; } Prevention
- Grant keys only needed permissions but verify against endpoint docs
- Re-check key scopes after Immich upgrades
- Test each key against the endpoints it will use
When it happens
Trigger: A request authenticates with an API key (authDto.apiKey set), requestedPermission is not false, and the key's permissions lack the requested permission.
Common situations: Using an Immich API key created without the needed scope (e.g. asset-scoped key used for library or admin operations); keys from older versions missing new permission slugs; sharing one key across many integrations.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- Sync endpoints cannot be used with API keys
- Authentication required
- Forbidden
- Cannot add another owner
- Cannot remove the last album owner
AI-assisted analysis of immich-app/immich@f48d4b3321 (2026-09-15).
Data as JSON: /api/errors/5d9ece26c6055f27.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/auth.service.ts:235
const { adminRoute, sharedLinkRoute, uri } = metadata;
const requestedPermission = metadata.permission ?? Permission.All;
if (!authDto.user.isAdmin && adminRoute) {
this.logger.warn(`Denied access to admin only route: ${uri}`);
throw new ForbiddenException('Forbidden');
}
if (authDto.sharedLink && !sharedLinkRoute) {
this.logger.warn(`Denied access to non-shared route: ${uri}`);
throw new ForbiddenException('Forbidden');
}
if (
authDto.apiKey &&
requestedPermission !== false &&
!isGranted({ requested: [requestedPermission], current: authDto.apiKey.permissions })
) {
throw new ForbiddenException(`Missing required permission: ${requestedPermission}`);
}
return authDto;
}
private async validate({ headers, queryParams }: Omit<ValidateRequest, 'metadata'>): Promise<AuthDto> {
const shareKey = (headers[ImmichHeader.SharedLinkKey] || queryParams[ImmichQuery.SharedLinkKey]) as string;
const shareSlug = (headers[ImmichHeader.SharedLinkSlug] || queryParams[ImmichQuery.SharedLinkSlug]) as string;
const session = (headers[ImmichHeader.UserToken] ||
headers[ImmichHeader.SessionToken] ||
queryParams[ImmichQuery.SessionKey] ||
this.getBearerToken(headers) ||
this.getCookieToken(headers)) as string;
const apiKey = (headers[ImmichHeader.ApiKey] || queryParams[ImmichQuery.ApiKey]) as string;
if (shareKey) {
return this.validateSharedLinkKey(shareKey);
}View on GitHub (pinned to f48d4b3321)