immich-app/immich · error · BadRequestException
Not found or no access
Error message
Not found or no ${request.permission} access What it means
requireAccess evaluates the requested permission over the given id set and compares the allowed set to the requested set. If they are not exactly equal — any requested id is missing, forbidden, or the resource does not exist — it throws a 400 Bad Request 'Not found or no <permission> access', deliberately indistinguishable between the two causes.
Solutions
- Verify every id in the request exists and belongs to you (or is shared with you) — fetch current lists before the bulk call.
- Check the required permission in the message and ensure the resource was shared with that permission level (e.g. edit vs view).
- Remove stale ids from client state and retry; refresh the album/asset list.
- For bulk requests, split into smaller batches and/or check access per id (checkAccess) to isolate the offending id.
Example fix
// before
await api.deleteAssets({ ids: allIds }); // one stale id fails everything
// after
const owned = await checkAccess(access, { auth, permission: 'asset.delete', ids: allIds });
await api.deleteAssets({ ids: [...owned] }); Defensive patterns
Strategy: validation
Validate before calling
const allowed = await checkAccess(access, { auth, permission: 'asset.delete', ids });
if (allowed.size !== ids.length) {
// filter to permitted ids or abort before the real call
ids = ids.filter((id) => allowed.has(id));
} Try / catch
try {
await api.deleteAlbums({ ids });
} catch (e) {
if (e instanceof BadRequestException && e.message.startsWith('Not found or no')) {
// refresh state; retry per-id to isolate the offending id
}
} Prevention
- Refresh resource lists before bulk operations to drop deleted ids.
- Verify share/permission level for shared resources before acting on them.
- Process bulk requests in chunks and check access per id.
- Never act on ids obtained from another user's responses.
When it happens
Trigger: Any endpoint that calls requireAccess (album/asset/partner/stack operations) with ids where at least one id either does not exist, belongs to another user, or the session lacks the requested permission (e.g. album.share, asset.delete) for it.
Common situations: Stale client references to deleted assets/albums; sharing links or collaboration where the partner hasn't granted the permission; IDs from another server/account; bulk operations where one id in the list fails and the whole request is rejected; race where a resource is deleted between listing and acting.
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
- Asset media not found
- Asset metadata is not yet ready for streaming
- Asset not found
- Asset not found
- Asset not found or asset is not a video
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/8ed44426914f4d84.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/utils/access.ts:40
auth: AuthDto;
permission: Permission;
ids: Set<string> | string[];
};
type SharedLinkAccessRequest = { sharedLink: AuthSharedLink; permission: Permission; ids: Set<string> };
type OtherAccessRequest = { auth: AuthDto; permission: Permission; ids: Set<string> };
export const requireUploadAccess = (auth: AuthDto | null): AuthDto => {
if (!auth || (auth.sharedLink && !auth.sharedLink.allowUpload)) {
throw new UnauthorizedException();
}
return auth;
};
export const requireAccess = async (access: AccessRepository, request: AccessRequest) => {
const allowedIds = await checkAccess(access, request);
if (!areSetsEqual(new Set(request.ids), allowedIds)) {
throw new BadRequestException(`Not found or no ${request.permission} access`);
}
};
export const checkAccess = async (
access: AccessRepository,
{ ids, auth, permission }: AccessRequest,
): Promise<Set<string>> => {
const idSet = Array.isArray(ids) ? new Set(ids) : ids;
if (idSet.size === 0) {
return new Set<string>();
}
return auth.sharedLink
? checkSharedLinkAccess(access, { sharedLink: auth.sharedLink, permission, ids: idSet })
: checkOtherAccess(access, { auth, permission, ids: idSet });
};
const checkSharedLinkAccess = async (View on GitHub (pinned to e55ac299a4)