Kareadita/Kavita · error · OpdsException

reading-list-restricted

Error message

reading-list-restricted

What it means

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.

Source

Thrown at Kavita.Services/OpdsService.cs:507

        SetFeedId(feed, $"library-{library.Name}");
        AddPagination(feed, series, $"{prefix}{apiKey}/libraries/{libraryId}");

        feed.Entries.AddRange(series.Select(seriesDto =>
            CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey, prefix, baseUrl)));

        return feed;
    }


    public async Task<Feed> GetReadingListItems(OpdsItemsFromEntityIdRequest request, CancellationToken ct = default)
    {
        var userId = UnpackRequest(request, out var apiKey, out var prefix, out _);
        var readingListId = request.EntityId;

        var readingList = await unitOfWork.ReadingListRepository.GetReadingListDtoByIdAsync(readingListId, userId, ct);
        if (readingList == null)
        {
            throw new OpdsException(await localizationService.TranslateAsync(request.UserId, "reading-list-restricted"));
        }

        var feed = CreateFeed(readingList.Title + " " + await localizationService.TranslateAsync(userId, "reading-list"), $"{apiKey}/reading-list/{readingListId}", apiKey, prefix);
        SetFeedId(feed, $"reading-list-{readingListId}");

        var items = await readingListService.GetReadingListItems(readingListId, userId, GetUserParams(request.PageNumber));
        var totalItems = await unitOfWork.ReadingListRepository .GetReadingListItemCountAsync(readingListId, userId, ct);

        var chapterIds = items.Select(i => i.ChapterId).Distinct().ToList();
        var chapters = (await unitOfWork.ChapterRepository .GetChapterDtosAsync(chapterIds, userId, ct))
            .ToDictionary(c => c.Id);

        // Check if there is reading progress or not, if so, inject a "continue-reading" item

        if (request.Preferences.IncludeContinueFrom && request.PageNumber == FirstPageNumber)
        {
            var anyProgress = await unitOfWork.ReadingListRepository.AnyUserReadingProgressAsync(readingListId, userId, ct);
            if (anyProgress)

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Refresh the reading-list listing in the OPDS client to see only accessible lists.
  2. If the list should be accessible, verify in Kavita's UI that it is shared or owned by the user.
  3. Confirm the readingListId in the URL is current and not from a cached page.
  4. Ensure the correct user's API key is used.
  5. If the list was deleted, recreate it or remove the bookmark from the OPDS reader.

Example fix

// No code fix — access/not-found issue.
// In Kavita UI: Reading Lists -> share the list with the user
// or make it public if cross-user access is needed.

// OPDS client should only navigate to reading lists returned
// in the user's reading-list feed.
Defensive patterns

Strategy: validation

Validate before calling

// Before requesting a reading-list feed, verify access:
// var readingList = await unitOfWork.ReadingListRepository
//     .GetReadingListDtoByIdAsync(readingListId, userId, ct);
// if (readingList == null)
// {
//     ShowUser("Reading list not found or not shared with you.");
//     return;
// }
// var feed = await opdsService.GetReadingListItems(request, ct);

Try / catch

// try { var feed = await opdsService.GetReadingListItems(request, ct); }
// catch (OpdsException ex) when (ex.Message.Contains("reading-list-restricted"))
// {
//     // Reading list deleted, unshared, or not owned by user.
//     await RefreshReadingListList();
//     return BadRequest(ex.Message);
// }

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13). Data as JSON: /api/errors/b69a82e4abf03107. Report an issue: GitHub.