immich-app/immich · warning · BadRequestException

Shared link access is only allowed in combination with an…

Error message

Shared link access is only allowed in combination with an albumIds filter

What it means

In searchMetadata, a request authenticated via a shared link must include a non-empty albumIds filter; otherwise the visitor would have access to assets outside the shared context. Since shared-link users have no own 'universe' of assets, the endpoint refuses the unscoped search with this BadRequest.

Solutions

  1. Add albumIds (of the shared album) to every search request made with a shared link
  2. Authenticate with a normal user session instead of a shared link when unscoped search is needed
  3. Check the client that albumIds are actually being forwarded for shared-link sessions
  4. Return an empty/filtered result in your UI when albumIds is absent for shared-link visitors

Example fix

// before
const res = await api.searchMetadata({}); // throws with shared link
// after
const res = await api.searchMetadata({ albumIds: [sharedAlbumId] });
Defensive patterns

Strategy: validation

Validate before calling

if (isSharedLinkSession() && !(dto.albumIds?.length)) {
  // don't call the API; show album-only UI
  return emptyResult();
}

Type guard

function isAlbumScopedSearch(dto: { albumIds?: string[] }): boolean {
  return Array.isArray(dto.albumIds) && dto.albumIds.length > 0;
}

Try / catch

try {
  return await searchService.searchMetadata(auth, dto);
} catch (e) {
  if (e instanceof BadRequestException && /albumIds filter/.test(e.message)) {
    return emptyResult();
  }
  throw e;
}

Prevention

When it happens

Trigger: GET /search/metadata with a shared-link key (x-immich-shared-link header / key param) but without albumIds, or with an empty albumIds array.

Common situations: Shared-link visitors hitting search APIs directly; frontend components calling metadata search without passing the album context; API integrations reusing code paths meant for logged-in users with a shared-link session.

Understand the failure class

Background: "Invalid query parameter" / "Failed to parse value of ...": fixing bad query string parameters across APIs — this error's family across 36 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/c7fb0cac50990523. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/search.service.ts:94

      return this.searchMetadataV3(auth, dto);
    }

    if (dto.visibility === AssetVisibility.Locked) {
      requireElevatedPermission(auth);
    }

    let checksum: Buffer | undefined;
    if (dto.checksum) {
      const encoding = dto.checksum.length === 28 ? 'base64' : 'hex';
      checksum = Buffer.from(dto.checksum, encoding);
    }

    let userIds: string[] | undefined;

    if (dto.albumIds && dto.albumIds.length > 0) {
      await this.requireAccess({ auth, ids: dto.albumIds, permission: Permission.AlbumRead });
    } else if (auth.sharedLink) {
      throw new BadRequestException('Shared link access is only allowed in combination with an albumIds filter');
    } else {
      userIds = await this.getUserIdsToSearch(auth, dto.visibility);
    }

    const page = dto.page ?? 1;
    const size = dto.size;
    const { hasNextPage, items } = await this.searchRepository.searchMetadata(
      { page, size },
      {
        ...dto,
        checksum,
        visibility: dto.visibility ?? (auth.session?.hasElevatedPermission ? undefined : 'not-locked'),
        userIds,
        viewingUserId: auth.user.id,
        orderDirection: dto.order ?? AssetOrder.Desc,
      },
    );

View on GitHub (pinned to e55ac299a4)