{"record":{"id":"2985e2dff83c998e","repo":"Kareadita/Kavita","slug":"collection-doesnt-exist-2985e2","errorCode":null,"errorMessage":"collection-doesnt-exist","messagePattern":"collection-doesnt-exist","errorType":"exception","errorClass":"OpdsException","httpStatus":null,"severity":"error","filePath":"Kavita.Services/OpdsService.cs","lineNumber":442,"sourceCode":"                break;\n            case FilterEntityType.Person:\n                throw new OpdsException(\"OPDS feed generation is not implemented for Person smart filters\");\n            case FilterEntityType.Annotation:\n                throw new OpdsException(\"OPDS feed generation is not implemented for Annotation smart filters\");\n        }\n\n        return feed;\n    }\n\n    public async Task<Feed> GetSeriesFromCollection(OpdsItemsFromEntityIdRequest request, CancellationToken ct = default)\n    {\n        var userId = UnpackRequest(request, out var apiKey, out var prefix, out var baseUrl);\n        var collectionId = request.EntityId;\n\n        var tag = await unitOfWork.CollectionTagRepository.GetCollectionAsync(collectionId, ct: ct);\n        if (tag == null || (tag.AppUserId != userId && !tag.Promoted))\n        {\n            throw new OpdsException(await localizationService.TranslateAsync(userId, \"collection-doesnt-exist\"));\n        }\n\n        var series = await unitOfWork.SeriesRepository.GetSeriesDtoForCollectionAsync(collectionId, userId, GetUserParams(request.PageNumber), ct);\n        var seriesMetadatas = await unitOfWork.SeriesRepository.GetSeriesMetadataForIdsAsync(series.Select(s => s.Id), ct);\n\n        var feed = CreateFeed(tag.Title + \" Collection\", $\"{apiKey}/collections/{collectionId}\", apiKey, prefix);\n        SetFeedId(feed, $\"collections-{collectionId}\");\n        AddPagination(feed, series, $\"{prefix}{apiKey}/collections/{collectionId}\");\n\n        foreach (var seriesDto in series)\n        {\n            feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey, prefix, baseUrl));\n        }\n\n        return feed;\n    }\n\n    public async Task<Feed> GetSeriesFromLibrary(OpdsItemsFromEntityIdRequest request, CancellationToken ct = default)","sourceCodeStart":424,"sourceCodeEnd":460,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/OpdsService.cs#L424-L460","documentation":"Thrown by OpdsService.GetSeriesFromCollection when the requested collection tag is null OR the requesting user does not own it and it is not promoted. The check is twofold: the collection must exist (GetCollectionAsync returns non-null), and if it exists, either AppUserId must match the requesting user or Promoted must be true (making it a public/shared collection). This is both a not-found and an access-control guard.","triggerScenarios":"An OPDS client requests {apiKey}/collections/{collectionId}. The collection was deleted, the ID is invalid, or the collection is private and belongs to a different user who has not promoted it.","commonSituations":"A collection was deleted from the Kavita UI but the OPDS reader cached the URL. A user is trying to access another user's private (non-promoted) collection. The collectionId in the URL is stale or corrupted.","solutions":["Refresh the collection listing in the OPDS client to get current, accessible collection IDs.","If the collection should be accessible, verify in Kavita's UI that it is Promoted (shared publicly) or owned by the requesting user.","Confirm the correct user's API key is being used; private collections are only visible to their owner.","Recreate the collection if it was accidentally deleted.","Check the CollectionTags table in the database to confirm the collection exists and its Promoted/owner status."],"exampleFix":"// No code fix — access control / not-found issue.\n// In Kavita UI: Collections -> edit collection -> toggle 'Promoted'\n// to make it accessible to other users via OPDS.\n\n// In the OPDS client, only navigate to collections returned by\n// the user's own collections feed.","handlingStrategy":"validation","validationCode":"// Before requesting a collection feed, verify access:\n// var tag = await unitOfWork.CollectionTagRepository.GetCollectionAsync(collectionId, ct: ct);\n// if (tag == null || (tag.AppUserId != userId && !tag.Promoted))\n// {\n//     ShowUser(\"Collection not found or access denied.\");\n//     return;\n// }\n// var feed = await opdsService.GetSeriesFromCollection(request, ct);","typeGuard":null,"tryCatchPattern":"// try { var feed = await opdsService.GetSeriesFromCollection(request, ct); }\n// catch (OpdsException ex)\n// {\n//     if (ex.Message.Contains(\"collection-doesnt-exist\"))\n//     {\n//         // Collection was deleted or access revoked.\n//         await RefreshCollectionList();\n//         return BadRequest(ex.Message);\n//     }\n//     throw;\n// }","preventionTips":["In OPDS clients, only navigate to collections from the user's live collection feed.","If a collection should be cross-user accessible, set it to Promoted in the Kavita UI.","Refresh the OPDS feed after creating, deleting, or modifying collection visibility.","Use the correct API key; private collections are owner-only.","Handle 'collection-doesnt-exist' in OPDS clients by refreshing the collection list."],"tags":["opds","collection","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"}