{"record":{"id":"b69a82e4abf03107","repo":"Kareadita/Kavita","slug":"reading-list-restricted","errorCode":null,"errorMessage":"reading-list-restricted","messagePattern":"reading-list-restricted","errorType":"exception","errorClass":"OpdsException","httpStatus":null,"severity":"error","filePath":"Kavita.Services/OpdsService.cs","lineNumber":507,"sourceCode":"        SetFeedId(feed, $\"library-{library.Name}\");\n        AddPagination(feed, series, $\"{prefix}{apiKey}/libraries/{libraryId}\");\n\n        feed.Entries.AddRange(series.Select(seriesDto =>\n            CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey, prefix, baseUrl)));\n\n        return feed;\n    }\n\n\n    public async Task<Feed> GetReadingListItems(OpdsItemsFromEntityIdRequest request, CancellationToken ct = default)\n    {\n        var userId = UnpackRequest(request, out var apiKey, out var prefix, out _);\n        var readingListId = request.EntityId;\n\n        var readingList = await unitOfWork.ReadingListRepository.GetReadingListDtoByIdAsync(readingListId, userId, ct);\n        if (readingList == null)\n        {\n            throw new OpdsException(await localizationService.TranslateAsync(request.UserId, \"reading-list-restricted\"));\n        }\n\n        var feed = CreateFeed(readingList.Title + \" \" + await localizationService.TranslateAsync(userId, \"reading-list\"), $\"{apiKey}/reading-list/{readingListId}\", apiKey, prefix);\n        SetFeedId(feed, $\"reading-list-{readingListId}\");\n\n        var items = await readingListService.GetReadingListItems(readingListId, userId, GetUserParams(request.PageNumber));\n        var totalItems = await unitOfWork.ReadingListRepository .GetReadingListItemCountAsync(readingListId, userId, ct);\n\n        var chapterIds = items.Select(i => i.ChapterId).Distinct().ToList();\n        var chapters = (await unitOfWork.ChapterRepository .GetChapterDtosAsync(chapterIds, userId, ct))\n            .ToDictionary(c => c.Id);\n\n        // Check if there is reading progress or not, if so, inject a \"continue-reading\" item\n\n        if (request.Preferences.IncludeContinueFrom && request.PageNumber == FirstPageNumber)\n        {\n            var anyProgress = await unitOfWork.ReadingListRepository.AnyUserReadingProgressAsync(readingListId, userId, ct);\n            if (anyProgress)","sourceCodeStart":489,"sourceCodeEnd":525,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/OpdsService.cs#L489-L525","documentation":"Thrown by OpdsService.GetReadingListItems when GetReadingListDtoByIdAsync returns null for the given readingListId and userId. This combined lookup checks both existence and access: a reading list that exists but is not accessible to the requesting user also returns null. The error message 'reading-list-restricted' indicates that the list either does not exist or is not shared with/owned by the user.","triggerScenarios":"An OPDS client requests {apiKey}/reading-list/{readingListId}. The reading list was deleted, belongs to another user without sharing, or the readingListId is invalid. Notably, the translation uses request.UserId rather than the locally unpacked userId for the error message (a minor inconsistency but functionally equivalent for OPDS since both come from the same request).","commonSituations":"A user deleted a reading list but their OPDS reader cached the URL. Another user's private reading list is being accessed. The reading list was unshared after the OPDS link was created.","solutions":["Refresh the reading-list listing in the OPDS client to see only accessible lists.","If the list should be accessible, verify in Kavita's UI that it is shared or owned by the user.","Confirm the readingListId in the URL is current and not from a cached page.","Ensure the correct user's API key is used.","If the list was deleted, recreate it or remove the bookmark from the OPDS reader."],"exampleFix":"// No code fix — access/not-found issue.\n// In Kavita UI: Reading Lists -> share the list with the user\n// or make it public if cross-user access is needed.\n\n// OPDS client should only navigate to reading lists returned\n// in the user's reading-list feed.","handlingStrategy":"validation","validationCode":"// Before requesting a reading-list feed, verify access:\n// var readingList = await unitOfWork.ReadingListRepository\n//     .GetReadingListDtoByIdAsync(readingListId, userId, ct);\n// if (readingList == null)\n// {\n//     ShowUser(\"Reading list not found or not shared with you.\");\n//     return;\n// }\n// var feed = await opdsService.GetReadingListItems(request, ct);","typeGuard":null,"tryCatchPattern":"// try { var feed = await opdsService.GetReadingListItems(request, ct); }\n// catch (OpdsException ex) when (ex.Message.Contains(\"reading-list-restricted\"))\n// {\n//     // Reading list deleted, unshared, or not owned by user.\n//     await RefreshReadingListList();\n//     return BadRequest(ex.Message);\n// }","preventionTips":["Share reading lists with users if cross-user OPDS access is needed.","In OPDS clients, only navigate to reading lists from the user's reading-list feed.","Refresh the OPDS feed after sharing or unsharing reading lists.","Use the correct API key; private reading lists are owner-only.","Handle 'reading-list-restricted' in OPDS clients by refreshing the reading-list feed."],"tags":["opds","reading-list","not-found","access-control","feed"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}