Kareadita/Kavita · error · KavitaException
response.ErrorMessage
Error message
response.ErrorMessage
What it means
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.
Source
Thrown at Kavita.Services/Plus/ScrobblingService.cs:1641
ToAuditParams(evt), AuditStatus.Failure, "unknown-series", userId: evt.AppUserId);
} else
{
if (!await _unitOfWork.ScrobbleRepository.HasErrorForSeries(evt.SeriesId))
{
_unitOfWork.ScrobbleRepository.Attach(new ScrobbleError()
{
Comment = response.ErrorMessage,
Details = data.SeriesName,
LibraryId = evt.LibraryId,
SeriesId = evt.SeriesId
});
}
evt.SetErrorMessage(response.ErrorMessage.StartsWith("Review") ? ReviewFailedErrorMessage : response.ErrorMessage);
}
throw new KavitaException(response.ErrorMessage);
}
#pragma warning disable S2139
catch (FlurlHttpException ex)
#pragma warning restore S2139
{
var errorMessage = await ex.GetResponseStringAsync();
// Trim quotes if the response is a JSON string
errorMessage = errorMessage.Trim('"');
if (errorMessage.Contains("Too Many Requests"))
{
_logger.LogInformation("Hit Too many requests while posting scrobble updates, will be retried in the next cycle");
await _auditService.LogAsync(KavitaPlusAuditCategory.Scrobble, KavitaPlusEventType.ScrobbleRateLimitHit,
AuditStatus.Failure, error: "rate-limit-hit");
throw new KavitaException(RateLimitHitErrorMessage);
}
View on GitHub (pinned to 9c3e540000)
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.
Example fix
// before: caller assumes scrobble always succeeds
await scrobblingService.Scrobble(evt);
// after: handle the known KavitaException shape and surface a user message
try { await scrobblingService.Scrobble(evt); }
catch (KavitaException ex) when (ex.Message.StartsWith("Review"))
{
_logger.LogWarning("Review scrobble rejected for series {Series}: {Msg}", evt.SeriesId, ex.Message);
}
catch (KavitaException ex)
{
_logger.LogError("Scrobble failed for series {Series}: {Msg}", evt.SeriesId, ex.Message);
} Defensive patterns
Strategy: try-catch
Validate before calling
// Validate the event + provider link before posting
if (evt == null) throw new ArgumentNullException(nameof(evt));
if (string.IsNullOrWhiteSpace(data.SeriesName)) return; // nothing to scrobble
var licenseActive = await _licenseService.HasActiveLicense();
if (!licenseActive) return;
var tokenValid = await _unitOfWork.ScrobbleRepository.IsProviderTokenValid(evt.ScrobbleProviderId);
if (!tokenValid) { _logger.LogWarning("Provider token invalid, skipping scrobble"); return; } Type guard
// C#
public static bool IsRecoverableScrobbleError(KavitaException ex) =>
ex.Message.StartsWith("Review") ||
ex.Message.Contains("rate limit", StringComparison.OrdinalIgnoreCase); Try / catch
try
{
await scrobblingService.Scrobble(evt);
}
catch (KavitaException ex) when (ex.Message.Contains("rate limit", StringComparison.OrdinalIgnoreCase))
{
_logger.LogInformation("Scrobble throttled; will retry next cycle for series {Id}", evt.SeriesId);
}
catch (KavitaException ex)
{
_logger.LogError("Scrobble rejected for series {Id}: {Msg}", evt.SeriesId, ex.Message);
// event already recorded as ScrobbleError; do not rethrow into the job loop
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/32a09c633cecd79c.
Report an issue: GitHub.