{"record":{"id":"32a09c633cecd79c","repo":"Kareadita/Kavita","slug":"response-errormessage","errorCode":null,"errorMessage":"response.ErrorMessage","messagePattern":"response\\.ErrorMessage","errorType":"exception","errorClass":"KavitaException","httpStatus":500,"severity":"error","filePath":"Kavita.Services/Plus/ScrobblingService.cs","lineNumber":1641,"sourceCode":"                    ToAuditParams(evt), AuditStatus.Failure, \"unknown-series\", userId: evt.AppUserId);\n\n            } else\n            {\n                if (!await _unitOfWork.ScrobbleRepository.HasErrorForSeries(evt.SeriesId))\n                {\n                    _unitOfWork.ScrobbleRepository.Attach(new ScrobbleError()\n                    {\n                        Comment = response.ErrorMessage,\n                        Details = data.SeriesName,\n                        LibraryId = evt.LibraryId,\n                        SeriesId = evt.SeriesId\n                    });\n                }\n\n                evt.SetErrorMessage(response.ErrorMessage.StartsWith(\"Review\") ? ReviewFailedErrorMessage : response.ErrorMessage);\n            }\n\n            throw new KavitaException(response.ErrorMessage);\n        }\n        #pragma warning disable S2139\n        catch (FlurlHttpException ex)\n        #pragma warning restore S2139\n        {\n            var errorMessage = await ex.GetResponseStringAsync();\n            // Trim quotes if the response is a JSON string\n            errorMessage = errorMessage.Trim('\"');\n\n            if (errorMessage.Contains(\"Too Many Requests\"))\n            {\n                _logger.LogInformation(\"Hit Too many requests while posting scrobble updates, will be retried in the next cycle\");\n                await _auditService.LogAsync(KavitaPlusAuditCategory.Scrobble, KavitaPlusEventType.ScrobbleRateLimitHit,\n                    AuditStatus.Failure, error: \"rate-limit-hit\");\n\n                throw new KavitaException(RateLimitHitErrorMessage);\n            }\n","sourceCodeStart":1623,"sourceCodeEnd":1659,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/Plus/ScrobblingService.cs#L1623-L1659","documentation":"Thrown by the Kavita+ scrobbling pipeline after it posts a scrobble event (review/rating/progress) to the Kavita+ API and the API returns a non-success response carrying an ErrorMessage. The upstream message is propagated verbatim to the UI, except when it starts with 'Review' it is softened to ReviewFailedErrorMessage on the event record. It is a KavitaException, so it surfaces to the client but is deliberately NOT reported to Sentry.","triggerScenarios":"Posting a scrobble event (ScrobbleEvent) to the Kavita+ scrobbling endpoint returns a response whose Success is false and ErrorMessage is populated. Concrete upstream causes: the matched external provider (MAL/AniList/Shikimori) rejected the review/rating payload, an access token expired or was revoked, or upstream validation failed (e.g. review length, score range).","commonSituations":"A user's Kavita+ license or external-provider link (AniList/MAL) token expired; the upstream provider changed an API contract after a Kavita upgrade; a review/rating scrobble carries data the provider now rejects; intermittent upstream 5xx that the retry layer already exhausted.","solutions":["Check the ScrobbleError row persisted via ScrobbleRepository.Attach (Comment = response.ErrorMessage, Details = series name) to see the exact upstream reason, and inspect server logs around the failed cycle.","Re-authenticate the affected scrobble provider in Settings > Account (Scrobbling) and verify the Kavita+ license is active.","If the message starts with 'Review', retry the scrobble in the next cycle after correcting the review/rating data on the series to satisfy upstream requirements.","If the upstream contract changed, update Kavita to the latest release and re-trigger the scrobble job."],"exampleFix":"// before: caller assumes scrobble always succeeds\nawait scrobblingService.Scrobble(evt);\n\n// after: handle the known KavitaException shape and surface a user message\ntry { await scrobblingService.Scrobble(evt); }\ncatch (KavitaException ex) when (ex.Message.StartsWith(\"Review\"))\n{\n    _logger.LogWarning(\"Review scrobble rejected for series {Series}: {Msg}\", evt.SeriesId, ex.Message);\n}\ncatch (KavitaException ex)\n{\n    _logger.LogError(\"Scrobble failed for series {Series}: {Msg}\", evt.SeriesId, ex.Message);\n}","handlingStrategy":"try-catch","validationCode":"// Validate the event + provider link before posting\nif (evt == null) throw new ArgumentNullException(nameof(evt));\nif (string.IsNullOrWhiteSpace(data.SeriesName)) return; // nothing to scrobble\nvar licenseActive = await _licenseService.HasActiveLicense();\nif (!licenseActive) return;\nvar tokenValid = await _unitOfWork.ScrobbleRepository.IsProviderTokenValid(evt.ScrobbleProviderId);\nif (!tokenValid) { _logger.LogWarning(\"Provider token invalid, skipping scrobble\"); return; }","typeGuard":"// C#\npublic static bool IsRecoverableScrobbleError(KavitaException ex) =>\n    ex.Message.StartsWith(\"Review\") ||\n    ex.Message.Contains(\"rate limit\", StringComparison.OrdinalIgnoreCase);","tryCatchPattern":"try\n{\n    await scrobblingService.Scrobble(evt);\n}\ncatch (KavitaException ex) when (ex.Message.Contains(\"rate limit\", StringComparison.OrdinalIgnoreCase))\n{\n    _logger.LogInformation(\"Scrobble throttled; will retry next cycle for series {Id}\", evt.SeriesId);\n}\ncatch (KavitaException ex)\n{\n    _logger.LogError(\"Scrobble rejected for series {Id}: {Msg}\", evt.SeriesId, ex.Message);\n    // event already recorded as ScrobbleError; do not rethrow into the job loop\n}","preventionTips":["Keep scrobble provider tokens fresh; surface token-expiry to the user before posting.","Validate review/rating payloads against provider constraints before scrobbling.","Run scrobble backfills in smaller batches to avoid triggering upstream rejections.","Persist the ScrobbleError so a persistently-failing series is skipped instead of retried indefinitely."],"tags":["scrobbling","kavita-plus","external-service","api"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}