immich-app/immich · error · NotFoundException
Asset not found or asset is not a video
Error message
Asset not found or asset is not a video
What it means
playbackVideo streams the video rendition (encoded video, falling back to the original path). getForVideo returns null when the id does not exist or the record is not a video asset, so the service throws NotFoundException('Asset not found or asset is not a video').
Solutions
- Check asset.type === 'VIDEO' client-side before calling the playback endpoint.
- Verify the asset id is valid and the asset still exists (GET /api/assets/:id).
- If the asset should be a video, confirm the upload finished and the type was detected correctly.
- Fall back to the thumbnail/preview endpoint for non-video assets.
Example fix
// before
await fetch(`${base}/api/assets/${id}/video/playback`);
// after
const asset = await (await fetch(`${base}/api/assets/${id}`)).json();
if (asset.type === 'VIDEO') await fetch(`${base}/api/assets/${id}/video/playback`); Defensive patterns
Strategy: validation
Validate before calling
const asset = await getAssetInfo(id);
if (asset.type !== 'VIDEO') throw new Error(`Asset ${id} is not a video`); Type guard
const isVideo = (a: { type: string }): a is { type: 'VIDEO' } => a.type === 'VIDEO'; Try / catch
try { return await getVideoPlayback(id); } catch (e) { if (e.status === 404) return null; throw e; } Prevention
- Check asset.type before choosing the playback endpoint
- Refresh cached asset lists to avoid stale ids
- Handle 404 by falling back to the asset's thumbnail in UIs
When it happens
Trigger: GET /api/assets/:id/video/playback with an id belonging to a photo/JPEG asset, a deleted asset, or a malformed/nonexistent id.
Common situations: Clients hardcoding a video endpoint for mixed asset lists; stale ids after an asset was deleted or the library was re-scanned; shared-link access to an asset excluded from the link.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- Asset not found
- Asset not found
- Both assets must exist
- Live photo video must be a video
- Live photo video not found
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/d3a03e4f56f42467.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/asset-media.service.ts:306
const fileNameBase =
auth.sharedLink && !auth.sharedLink.showExif ? id : getFileNameWithoutExtension(originalFileName);
const fileName = `${fileNameBase}_${size}${getFilenameExtension(path)}`;
return new ImmichFileResponse({
fileName,
path,
contentType: mimeTypes.lookup(path),
cacheControl: CacheControl.PrivateWithCache,
});
}
async playbackVideo(auth: AuthDto, id: string): Promise<ImmichFileResponse> {
await this.requireAccess({ auth, permission: Permission.AssetView, ids: [id] });
const asset = await this.assetRepository.getForVideo(id);
if (!asset) {
throw new NotFoundException('Asset not found or asset is not a video');
}
const filepath = asset.encodedVideoPath || asset.originalPath;
return new ImmichFileResponse({
path: filepath,
contentType: mimeTypes.lookup(filepath),
cacheControl: CacheControl.PrivateWithCache,
});
}
async bulkUploadCheck(auth: AuthDto, dto: AssetBulkUploadCheckDto): Promise<AssetBulkUploadCheckResponseDto> {
const checksums: Buffer[] = dto.assets.map((asset) => fromChecksum(asset.checksum));
const results = await this.assetRepository.getByChecksums(auth.user.id, checksums);
const checksumMap: Record<string, { id: string; isTrashed: boolean }> = {};
for (const { id, deletedAt, checksum } of results) {
checksumMap[checksum.toString('hex')] = { id, isTrashed: !!deletedAt };View on GitHub (pinned to e55ac299a4)