Kareadita/Kavita · error · OpdsException

chapter-doesnt-exist

Error message

chapter-doesnt-exist

What it means

Thrown by OpdsService.GetItemsFromChapter when the chapter is not found within the volume's chapter collection. After the series and volume checks pass, the service does volume.Chapters.FirstOrDefault(c => c.Id == chapterId). If this returns null, the chapterId does not belong to the validated volume. This is distinct from a volume-level null: the volume exists but doesn't contain this chapter.

Source

Thrown at Kavita.Services/OpdsService.cs:704

        var chapterId = request.ChapterId;

        var series = await unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId, ct);
        if (series == null)
        {
            throw new OpdsException(await localizationService.TranslateAsync(userId, "series-doesnt-exist"));
        }

        var volume = await unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId,  userId, ct);
        if (volume == null)
        {
            throw new OpdsException(await localizationService.TranslateAsync(userId, "volume-doesnt-exist"));
        }

        var libraryType = await unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId, ct);
        var chapter = volume.Chapters.FirstOrDefault(c => c.Id == chapterId);
        if (chapter == null)
        {
            throw new OpdsException(await localizationService.TranslateAsync(userId, "chapter-doesnt-exist"));
        }

        var namingContext = await LocalizedNamingContext.CreateAsync(namingService, localizationService, userId, libraryType);
        var chapterName = namingContext.FormatChapterTitle(chapter);

        var feed = CreateFeed( $"{series.Name} - Volume {volume.Name} - {chapterName} {chapterId}",
            $"{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}", apiKey, prefix);
        SetFeedId(feed, $"series-{series.Id}-volume-{volumeId}-{chapterId}-files");


        feed.Entries.Add(CreateChapterFeedEntry(series, volume, chapter, namingContext, request));

        return feed;
    }

    public async Task<Feed> Search(OpdsSearchRequest request, CancellationToken ct = default)
    {
        var userId = UnpackRequest(request, out var apiKey, out var prefix, out var baseUrl);

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Navigate back to the volume feed in the OPDS client to get current chapter IDs.
  2. Rescan the library if chapters were restructured.
  3. Verify the chapterId belongs to the specified volumeId.
  4. Refresh the OPDS client's cache after library rescans.
  5. Check Kavita's UI to confirm the chapter exists under the expected volume.

Example fix

// No code fix — chapter not in volume.
// OPDS client: go back to the volume feed and re-select the
// chapter to get a current URL.

// Verify chapterId is within volumeId's chapters:
// GET {apiKey}/series/{seriesId}/volume/{volumeId}
//   -> list of valid chapter IDs in that volume
Defensive patterns

Strategy: validation

Validate before calling

// Before requesting a chapter feed, verify the full chain:
// var series = await unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId, ct);
// if (series == null) { ShowUser("Series not found."); return; }
// var volume = await unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, userId, ct);
// if (volume == null) { ShowUser("Volume not found."); return; }
// if (volume.Chapters.All(c => c.Id != chapterId))
// {
//     ShowUser("Chapter not found in this volume.");
//     return;
// }
// var feed = await opdsService.GetItemsFromChapter(request, ct);

Try / catch

// try { var feed = await opdsService.GetItemsFromChapter(request, ct); }
// catch (OpdsException ex) when (ex.Message.Contains("chapter-doesnt-exist"))
// {
//     // Chapter not in this volume (moved or deleted during rescan).
//     await RefreshVolumeFeed(seriesId, volumeId);
//     return BadRequest(ex.Message);
// }

Prevention

When it happens

Trigger: An OPDS client requests {apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}. The series and volume are valid and accessible, but the chapterId doesn't match any chapter in volume.Chapters. This can happen if the chapter was moved to a different volume during rescan, deleted, or the chapterId is from a different volume.

Common situations: A library rescan restructured chapters (e.g., a chapter moved from Volume 1 to Volume 2). The chapterId is from a different volume than volumeId. The chapter was a special or extra that got re-categorized. The OPDS reader cached an old URL where the chapter-volume mapping changed.

Related errors


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