immich-app/immich · error · BadRequestException
You may not access another user's locked timeline
Error message
You may not access another user's locked timeline
What it means
Thrown (as BadRequestException) by TimelineService when the requested visibility is AssetVisibility.Locked and the target userId is not the authenticated user. Locked timelines are private by design; even admins/partners cannot browse another user's locked assets through the timeline endpoint.
Source
Thrown at server/src/services/timeline.service.ts:64
private async timeBucketChecks(auth: AuthDto, dto: TimeBucketDto) {
if (dto.visibility === AssetVisibility.Locked) {
requireElevatedPermission(auth);
}
if (dto.albumId) {
await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [dto.albumId] });
} else {
dto.userId ||= auth.user.id;
}
if (dto.userId) {
await this.requireAccess({ auth, permission: Permission.TimelineRead, ids: [dto.userId] });
if (dto.visibility === AssetVisibility.Archive) {
await this.requireAccess({ auth, permission: Permission.ArchiveRead, ids: [dto.userId] });
}
if (dto.visibility === AssetVisibility.Locked && dto.userId !== auth.user.id) {
throw new BadRequestException("You may not access another user's locked timeline");
}
}
if (dto.tagId) {
await this.requireAccess({ auth, permission: Permission.TagRead, ids: [dto.tagId] });
}
if (auth.sharedLink && !auth.sharedLink.showExif) {
dto.withCoordinates = false;
}
if (dto.withPartners) {
const isRequestedLocked = dto.visibility === AssetVisibility.Locked;
const isRequestedArchived = dto.visibility === AssetVisibility.Archive || dto.visibility === undefined;
const isRequestedFavorite = dto.isFavorite === true || dto.isFavorite === false;
const isRequestedTrash = dto.isTrashed === true;
if (isRequestedLocked || isRequestedArchived || isRequestedFavorite || isRequestedTrash) {View on GitHub (pinned to 199723261c)
Solutions
- Omit visibility=Locked when querying another user; locked assets are only self-visible.
- If you need the current user's locked items, ensure dto.userId equals auth.user.id (or omit userId).
- Use the admin asset list endpoint if a true audit of locked assets is required.
Example fix
// before
buckets({ userId: otherUserId, visibility: AssetVisibility.Locked });
// after
buckets({ userId: auth.user.id, visibility: AssetVisibility.Locked }); Defensive patterns
Strategy: validation
Validate before calling
function canViewLocked(authUserId: string, targetUserId: string | undefined, vis: AssetVisibility) {
return vis !== AssetVisibility.Locked || targetUserId === undefined || targetUserId === authUserId;
}
if (!canViewLocked(auth.user.id, dto.userId, dto.visibility)) { /* hide the locked filter */ } Try / catch
try { await timelineApi.buckets(dto); }
catch (e) {
if (e instanceof BadRequestException && /locked timeline/.test(e.message)) {
// clear visibility=Locked when userId != self
}
} Prevention
- Hide the 'Locked' filter when viewing another user's timeline.
- Default dto.userId to auth.user.id for self views.
- Use admin asset endpoints for cross-user audits instead of timeline.
When it happens
Trigger: GET timeline/buckets or timeline/search with visibility=Locked and userId pointing at a different user than auth.user.id.
Common situations: Admin tooling tries to audit locked assets of a user; partner UI accidentally requests a partner's locked scope; client passed the wrong userId.
Related errors
- withPartners is only supported for non-archived, non-trashed
- Admin status can only be changed by another admin
- assetIds, albumId, or userId is required
- Invalid job name
- Library ${id} not found
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/cc80d8f5919a49c2.
Report an issue: GitHub.