{"record":{"id":"519c216e9c70d8ec","repo":"Kareadita/Kavita","slug":"series-doesnt-exist-519c21","errorCode":null,"errorMessage":"series-doesnt-exist","messagePattern":"series-doesnt-exist","errorType":"exception","errorClass":"OpdsException","httpStatus":null,"severity":"error","filePath":"Kavita.Services/OpdsService.cs","lineNumber":565,"sourceCode":"            }\n\n            feed.Entries.Add(CreateReadingListEntry(item, chapterDto, request));\n        }\n\n        AddPagination(feed, request.PageNumber, totalItems, UserParams.Default.PageSize, $\"{prefix}{apiKey}/reading-list/{readingListId}/\");\n\n        return feed;\n    }\n\n    public async Task<Feed> GetSeriesDetail(OpdsItemsFromEntityIdRequest request, CancellationToken ct = default)\n    {\n        var userId = UnpackRequest(request, out var apiKey, out var prefix, out var baseUrl);\n        var seriesId = request.EntityId;\n\n        var series = await unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId, ct);\n        if (series == null)\n        {\n            throw new OpdsException(await localizationService.TranslateAsync(userId, \"series-doesnt-exist\"));\n        }\n\n        var seriesDetailTask = seriesService.GetSeriesDetail(seriesId, userId, ct);\n        var libraryTypeTask = unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId, ct);\n\n        await Task.WhenAll(seriesDetailTask, libraryTypeTask);\n\n        var seriesDetail = await seriesDetailTask;\n        var libraryType = await libraryTypeTask;\n\n        var namingContext = await LocalizedNamingContext.CreateAsync(namingService, localizationService, userId, libraryType);\n        var volumesById = seriesDetail.Volumes.ToDictionary(v => v.Id);\n\n        var feed = CreateFeed(series.Name + \" - Storyline\", $\"{apiKey}/series/{series.Id}\", apiKey, prefix);\n        SetFeedId(feed, $\"series-{series.Id}\");\n        feed.Links.Add(CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $\"{baseUrl}api/image/series-cover?seriesId={seriesId}&apiKey={apiKey}\"));\n\n","sourceCodeStart":547,"sourceCodeEnd":583,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/OpdsService.cs#L547-L583","documentation":"Thrown by OpdsService.GetSeriesDetail when GetSeriesDtoByIdAsync returns null for the given seriesId and userId. This combined lookup verifies both that the series exists and that the requesting user has library access to it. The controller also applies a [SeriesAccess] attribute as a first-line guard, so reaching this throw means the attribute check passed but the DB-level access query returned null — typically a race condition or stale ID.","triggerScenarios":"An OPDS client requests {apiKey}/series/{seriesId}. The series was deleted, the library containing it was revoked from the user, or seriesId is invalid. Because [SeriesAccess] already validates access at the controller level, this throw fires mainly in race conditions (series deleted between the attribute check and the service call).","commonSituations":"A series was removed from the library (file deleted and library scanned) while the OPDS reader had the URL cached. An admin revoked library access between two requests. The seriesId in the URL is corrupted or from a very old session.","solutions":["Refresh the series listing in the OPDS client to obtain current series IDs.","Verify in Kavita's UI that the series still exists and the user has library access.","If files were removed, rescan the library to update Kavita's catalog.","Confirm the correct API key is being used.","If the [SeriesAccess] attribute and this DB check disagree due to caching, restart Kavita to clear in-memory caches."],"exampleFix":"// No code fix — stale reference or access revocation.\n// In OPDS client, navigate back to the library feed and\n// re-select the series to get a current URL.\n\n// If developing an OPDS client, handle 400 responses with\n// 'series-doesnt-exist' by refreshing the parent feed.","handlingStrategy":"validation","validationCode":"// Before requesting a series detail feed, verify existence/access:\n// var series = await unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId, ct);\n// if (series == null)\n// {\n//     ShowUser(\"Series not found or access denied.\");\n//     return;\n// }\n// var feed = await opdsService.GetSeriesDetail(request, ct);","typeGuard":null,"tryCatchPattern":"// try { var feed = await opdsService.GetSeriesDetail(request, ct); }\n// catch (OpdsException ex) when (ex.Message.Contains(\"series-doesnt-exist\"))\n// {\n//     // Series was deleted or access revoked since the OPDS client cached the URL.\n//     await RefreshSeriesList();\n//     return BadRequest(ex.Message);\n// }","preventionTips":["In OPDS clients, always navigate from the live library feed rather than using cached series URLs.","After library rescans or file removals, refresh the OPDS feed.","Handle 'series-doesnt-exist' by refreshing the parent library feed.","Use the correct API key; series visibility depends on library-access roles.","Avoid hardcoding series IDs in scripts or bookmarks."],"tags":["opds","series","not-found","access-control","feed","stale-reference"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}