immich-app/immich · error · BadRequestException

Not found or no ${request.permission} access

Error message

Not found or no ${request.permission} access

What it means

A BadRequestException (HTTP 400) thrown by requireAccess when the set of resource ids the user is allowed to act on does not exactly equal the set of ids requested. checkAccess returns the allowed id subset; if any requested id is missing (either it does not exist or the user lacks the permission), the whole request is rejected.

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 199723261c)

Solutions

  1. Verify each id belongs to the calling user (or is shared with them) before the request.
  2. Refresh client-side id lists after deletions to avoid referencing removed resources.
  3. For batch operations, split ids and operate only on those the user owns, or request access to the others.
  4. Check the exact permission in the error message to know which access path failed.

Example fix

// before
await api.deleteAssets(['a','b','c']); // b owned by someone else -> 400

// after
const owned = await api.getMyAssets();
const deletable = ['a','b','c'].filter((id) => owned.some((a) => a.id === id));
await api.deleteAssets(deletable);
Defensive patterns

Strategy: validation

Validate before calling

async function filterAccessibleIds(auth, permission, ids) {
  // use a bulk access check / ownership filter before the mutating call
  const owned = await api.checkAccess(permission, ids);
  return ids.filter((id) => owned.has(id));
}
const safeIds = await filterAccessibleIds(auth, permission, ids);
if (safeIds.length !== ids.length) {
  return badRequest(`Missing access for ${ids.length - safeIds.length} id(s)`);
}

Type guard

const isAccessDeniedError = (e: unknown): boolean =>
  typeof e === 'object' && e !== null && (e as any).status === 400 &&
  typeof (e as any).message === 'string' && (e as any).message.startsWith('Not found or no');

Try / catch

try {
  await api.deleteAssets(ids);
} catch (e) {
  if (isAccessDeniedError(e)) {
    // retry with only owned ids discovered via search
    const owned = await getOwnedIds();
    await api.deleteAssets(ids.filter((id) => owned.includes(id)));
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling any access-gated operation (album update, asset delete, person merge, workflow delete, etc.) with one or more ids the caller does not own or that do not exist. The message includes the permission name, e.g., 'Not found or no AssetUpdate access'.

Common situations: Operating on a resource id from another user; stale client id after a resource was deleted; mixed batch where some ids are valid and others are not; permission not granted (e.g., non-owner trying an owner-only action).

Related errors


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/8ed44426914f4d84. Report an issue: GitHub.