{"record":{"id":"8ebbddca1ec2a1db","repo":"Sonarr/Sonarr","slug":"api-limit-reached","errorCode":null,"errorMessage":"API limit reached","messagePattern":"API limit reached","errorType":"exception","errorClass":"RequestLimitReachedException","httpStatus":null,"severity":"warning","filePath":"src/NzbDrone.Core/Indexers/Newznab/NewznabRssParser.cs","lineNumber":47,"sourceCode":"                return;\n            }\n\n            var code = Convert.ToInt32(error.Attribute(\"code\").Value);\n            var errorMessage = error.Attribute(\"description\").Value;\n\n            if (code >= 100 && code <= 199)\n            {\n                throw new ApiKeyException(errorMessage);\n            }\n\n            if (!indexerResponse.Request.Url.FullUri.Contains(\"apikey=\") && (errorMessage == \"Missing parameter\" || errorMessage.Contains(\"apikey\")))\n            {\n                throw new ApiKeyException(\"Indexer requires an API key\");\n            }\n\n            if (errorMessage == \"Request limit reached\")\n            {\n                throw new RequestLimitReachedException(\"API limit reached\");\n            }\n\n            throw new NewznabException(indexerResponse, errorMessage);\n        }\n\n        protected override bool PreProcess(IndexerResponse indexerResponse)\n        {\n            if (indexerResponse.HttpResponse.HasHttpError &&\n                (indexerResponse.HttpResponse.Headers.ContentType == null || !indexerResponse.HttpResponse.Headers.ContentType.Contains(\"xml\")))\n            {\n                base.PreProcess(indexerResponse);\n            }\n\n            var xdoc = LoadXmlDocument(indexerResponse);\n\n            CheckError(xdoc, indexerResponse);\n\n            return true;","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/Sonarr/Sonarr/blob/da2284d7eaab22ed9b2ca698af690148d691e967/src/NzbDrone.Core/Indexers/Newznab/NewznabRssParser.cs#L29-L65","documentation":"Thrown by NewznabRssParser.CheckError when the Newznab XML error element's description is exactly 'Request limit reached'. This is Newznab's standard rate-limiting message. The exception type is RequestLimitReachedException, which the HttpIndexerBase pipeline handles specially — it records a failure with an exponential backoff and temporarily disables the indexer until the retry time.","triggerScenarios":"A Newznab API request returns an XML error element with description 'Request limit reached', indicating the indexer's per-user or per-IP API rate limit has been exceeded. Newznab indexers typically enforce limits on requests per day or per hour.","commonSituations":"Sonarr polling too frequently (RSS sync interval too short); multiple applications (Sonarr, Radarr, Lidarr) sharing the same Newznab API key and collectively exceeding the limit; the Newznab provider reduced its API quota; aggressive manual searches; a bug causing duplicate requests.","solutions":["Wait for the rate limit window to reset — the indexer is auto-disabled with backoff, check the indexer status panel for the retry time.","Increase the RSS sync interval in Sonarr settings to reduce request frequency.","If multiple apps share the same API key, either get separate keys or coordinate request timing.","Upgrade to a higher API tier on the Newznab provider if available (VIP/donor often have higher limits).","Reduce the number of concurrent or manual searches."],"exampleFix":"// before — standard RequestLimitReachedException for 'Request limit reached'\nif (errorMessage == \"Request limit reached\")\n{\n    throw new RequestLimitReachedException(\"API limit reached\");\n}\n\n// after — parse Retry-After or rate-limit headers for precise backoff\n// var retryAfter = indexerResponse.HttpResponse.Headers.GetValueOrNull(\"Retry-After\");\n// var retryDelay = retryAfter != null\n//     ? TimeSpan.FromSeconds(int.Parse(retryAfter))\n//     : TimeSpan.FromMinutes(15);\n// throw new RequestLimitReachedException(retryDelay, \"Newznab API limit reached, retry after {0}\", retryDelay);","handlingStrategy":"retry","validationCode":"// Before fetching, check if the indexer is in a rate-limit cooldown\nvar blockedProviders = _indexerStatusService.GetBlockedProviders();\nif (blockedProviders.TryGetValue(indexer.Definition.Id, out var status))\n{\n    var disabledTill = status.DisabledTill;\n    if (disabledTill.HasValue && disabledTill.Value > DateTime.UtcNow)\n    {\n        _logger.Debug(\"Skipping indexer {0}, rate-limited until {1}\", indexer.Name, disabledTill);\n        return Array.Empty<ReleaseInfo>();\n    }\n}","typeGuard":null,"tryCatchPattern":"// RequestLimitReachedException is caught by HttpIndexerBase, which records\n// a failure with exponential backoff and temporarily disables the indexer.\n// For custom callers:\ntry\n{\n    return await FetchPage(request, parser);\n}\ncatch (RequestLimitReachedException ex)\n{\n    var retryAfter = ex.RetryAfter != TimeSpan.Zero ? ex.RetryAfter : TimeSpan.FromMinutes(15);\n    _logger.Warn(\"Newznab rate-limited, will retry after {0}\", retryAfter);\n    await Task.Delay(retryAfter);\n    return Array.Empty<ReleaseInfo>(); // skip this cycle\n}","preventionTips":["Set the RSS sync interval high enough to stay within the Newznab provider's daily/hourly quota.","Do not share the same API key across multiple applications.","Upgrade to a VIP/donor tier on the Newznab provider for higher limits if available.","Monitor the indexer status panel for backoff times and plan accordingly.","Avoid aggressive manual searches during peak hours."],"tags":["indexer","newznab","rate-limit","api-quota","transient"],"backgroundTag":null,"analyzedSha":"da2284d7eaab22ed9b2ca698af690148d691e967","analyzedAt":"2026-08-13T15:53:02.562Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}