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

  1. Verify every id in the request exists and belongs to you (or is shared with you) — fetch current lists before the bulk call.
  2. Check the required permission in the message and ensure the resource was shared with that permission level (e.g. edit vs view).
  3. Remove stale ids from client state and retry; refresh the album/asset list.
  4. 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

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


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)