immich-app/immich · error · BadRequestException

Invalid shared link type

Error message

Invalid shared link type

What it means

Immich throws this BadRequestException when a client tries to add assets to a shared link whose type is not 'Individual'. Only individual shared links allow direct asset management; album shared links derive their assets from the linked album, so mutating their asset list is invalid.

Solutions

  1. Verify the shared link's type is SharedLinkType.Individual before calling addAssets (GET /shared-links or check the link object).
  2. If the goal is to change album-link contents, update the linked album's assets instead of the shared link.
  3. Create a new Individual shared link containing the desired assets if direct asset control is needed.
  4. Handle 400 responses by checking the link type from the API response before retrying.

Example fix

// before
await api.addSharedLinkAssets(auth, albumLinkId, { assetIds });
// after
const link = await api.getSharedLink(auth, albumLinkId);
if (link.type === 'Individual') {
  await api.addSharedLinkAssets(auth, albumLinkId, { assetIds });
}
Defensive patterns

Strategy: validation

Validate before calling

const link = await api.getSharedLink(auth, id);
if (link.type !== 'Individual') throw new Error(`Link ${id} is not individual; cannot add assets`);

Type guard

const isIndividualLink = (link: { type: string }): link is { type: 'Individual' } => link.type === 'Individual';

Try / catch

try {
  await api.addSharedLinkAssets(auth, id, { assetIds });
} catch (e) {
  if (e.status === 400 && /Invalid shared link type/.test(e.message)) {
    // fall back to album-based update or surface a user-facing hint
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the addAssets endpoint (POST /shared-links/:id/assets) with the id of an album-type (non-Individual) shared link, e.g. a link created for a whole album.

Common situations: Scripts or mobile clients iterating over shared links and applying asset updates to all of them; a link's type changed semantics between API versions; the client cached the wrong link id.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at server/src/services/shared-link.service.ts:153

    } catch (error) {
      this.handleError(error);
    }
  }

  async remove(auth: AuthDto, id: string): Promise<void> {
    const sharedLink = await this.findOrFail(auth.user.id, id);
    await this.sharedLinkRepository.remove(sharedLink.id);
  }

  // TODO: replace `userId` with permissions and access control checks
  private findOrFail(userId: string, id: string) {
    return findOrFail(() => this.sharedLinkRepository.get(userId, id), 'Shared link');
  }

  async addAssets(auth: AuthDto, id: string, dto: AssetIdsDto): Promise<AssetIdsResponseDto[]> {
    const sharedLink = await this.findOrFail(auth.user.id, id);
    if (sharedLink.type !== SharedLinkType.Individual) {
      throw new BadRequestException('Invalid shared link type');
    }

    const existingAssetIds = new Set(sharedLink.assets.map((asset) => asset.id));
    const notPresentAssetIds = dto.assetIds.filter((assetId) => !existingAssetIds.has(assetId));
    const allowedAssetIds = await this.checkAccess({
      auth,
      permission: Permission.AssetShare,
      ids: notPresentAssetIds,
    });

    const results: AssetIdsResponseDto[] = [];
    for (const assetId of dto.assetIds) {
      const hasAsset = existingAssetIds.has(assetId);
      if (hasAsset) {
        results.push({ assetId, success: false, error: AssetIdErrorReason.DUPLICATE });
        continue;
      }

View on GitHub (pinned to e55ac299a4)