{"record":{"id":"41af444ecec10dd0","repo":"immich-app/immich","slug":"may-not-request-original-file","errorCode":null,"errorMessage":"May not request original file","messagePattern":"May not request original file","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/asset-media.service.ts","lineNumber":259,"sourceCode":"    const path = editedPath ?? originalPath!;\n\n    return new ImmichFileResponse({\n      path,\n      fileName: getFileNameWithoutExtension(originalFileName) + getFilenameExtension(path),\n      contentType: mimeTypes.lookup(path),\n      cacheControl: CacheControl.PrivateWithCache,\n    });\n  }\n\n  async viewThumbnail(\n    auth: AuthDto,\n    id: string,\n    dto: AssetMediaOptionsDto,\n  ): Promise<ImmichFileResponse | AssetMediaRedirectResponse> {\n    await this.requireAccess({ auth, permission: Permission.AssetView, ids: [id] });\n\n    if (dto.size === AssetMediaSize.Original) {\n      throw new BadRequestException('May not request original file');\n    }\n\n    if (auth.sharedLink) {\n      dto.edited = true;\n    }\n\n    const size = (dto.size ?? AssetMediaSize.THUMBNAIL) as unknown as AssetFileType;\n    const { originalPath, originalFileName, path } = await this.assetRepository.getForThumbnail(\n      id,\n      size,\n      dto.edited ?? false,\n    );\n\n    if (size === AssetFileType.FullSize && mimeTypes.isWebSupportedImage(originalPath) && !dto.edited) {\n      // use original file for web supported images\n      return { targetSize: 'original' };\n    }\n","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/asset-media.service.ts#L241-L277","documentation":"viewThumbnail serves thumbnail/preview/fullsize renditions of an asset. Requesting size=original through this endpoint is explicitly rejected because original files must be fetched via the dedicated original-file endpoint (viewOriginal), which performs different access checks (e.g. shared-link download permissions).","triggerScenarios":"GET /api/assets/:id/thumbnail?size=original (AssetMediaSize.ORIGINAL) — passing AssetMediaSize.Original in the dto to viewThumbnail.","commonSituations":"Client code copying URL templates and reusing size=original for all sizes; older API clients built before the original/thumbnail endpoints were split; hand-written scripts generating thumbnail URLs.","solutions":["Request size=preview or size=fullsize on the thumbnail endpoint.","To get the original file, call the original endpoint: GET /api/assets/:id/original.","Update outdated client SDK versions that still map 'original' to the thumbnail route."],"exampleFix":"// before\nawait fetch(`${base}/api/assets/${id}/thumbnail?size=original`);\n// after\nawait fetch(`${base}/api/assets/${id}/original`); // originals use the dedicated endpoint","handlingStrategy":"validation","validationCode":"if (size === 'original') throw new Error('Use /original endpoint for original files');\nconst url = size === 'original' ? `/api/assets/${id}/original` : `/api/assets/${id}/thumbnail?size=${size}`;","typeGuard":"const isThumbnailSize = (s: string): s is 'preview' | 'fullsize' => s === 'preview' || s === 'fullsize';","tryCatchPattern":null,"preventionTips":["Never pass size=original to the thumbnail endpoint","Centralize renditions URL building in one helper","Update SDKs after API surface changes (thumbnail vs original split)"],"tags":["bad-request","thumbnail","invalid-parameter","api-misuse"],"backgroundTag":"invalid-enum-value","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}