{"record":{"id":"b061fd19a5335044","repo":"App-vNext/Polly","slug":"the-operation-has-been-rate-limited-and-should-be-b061fd","errorCode":null,"errorMessage":"The operation has been rate-limited and should be retried after {retryAfter}","messagePattern":"The operation has been rate-limited and should be retried after (.+?)","errorType":"exception","errorClass":"RateLimitRejectedException","httpStatus":null,"severity":"warning","filePath":"src/Polly/RateLimit/RateLimitEngine.cs","lineNumber":25,"sourceCode":"        IRateLimiter rateLimiter,\n        Func<TimeSpan, Context, TResult>? retryAfterFactory,\n        Func<Context, CancellationToken, TResult> action,\n        Context context,\n        CancellationToken cancellationToken)\n    {\n        (bool permit, TimeSpan retryAfter) = rateLimiter.PermitExecution();\n\n        if (permit)\n        {\n            return action(context, cancellationToken);\n        }\n\n        if (retryAfterFactory != null)\n        {\n            return retryAfterFactory(retryAfter, context);\n        }\n\n        throw new RateLimitRejectedException(retryAfter);\n    }\n}\n","sourceCodeStart":7,"sourceCodeEnd":28,"githubUrl":"https://github.com/App-vNext/Polly/blob/d0e46bdb1ee11ea50d0e4b6846d2633d6bc09bac/src/Polly/RateLimit/RateLimitEngine.cs#L7-L28","documentation":"Thrown by RateLimitEngine.Implementation (synchronous) when the rate limiter denies execution and no retryAfterFactory was supplied. The RateLimitRejectedException carries RetryAfter computed by the token-bucket limiter. This is the sync counterpart of error 147 and represents an intentional throttle signal, not a fault.","triggerScenarios":"A synchronous RateLimitPolicy (no retryAfterFactory) executes a delegate while the bucket is empty; PermitExecution returns (false, retryAfter), retryAfterFactory is null, so the engine throws RateLimitRejectedException(retryAfter). Sustained load exceeding the configured limit triggers it.","commonSituations":"Load above the configured numberOfExecutions/perTimeSpan; maxBurst too low for traffic spikes; no retry policy wrapped around the rate-limited call; a retryAfterFactory intended to absorb rejection was omitted.","solutions":["Wrap the rate-limited policy in a Retry policy whose backoff reads exception.RetryAfter.","Pass a retryAfterFactory to RateLimit to return a fallback TResult instead of throwing.","Right-size numberOfExecutions and maxBurst from measured throughput.","Catch RateLimitRejectedException at the call site and back off using RetryAfter."],"exampleFix":"// before\nvar rl = Policy.RateLimit(10, TimeSpan.FromSeconds(1), 10);\nrl.Execute(ctx => doWork(ctx), new Context(), CancellationToken.None);\n\n// after\nvar retry = Policy.Handle<RateLimitRejectedException>()\n    .WaitAndRetry((_, ex) => ((RateLimitRejectedException)ex).RetryAfter.Add(TimeSpan.FromMilliseconds(50)));\nvar wrap = Policy.Wrap(retry, rl);\nwrap.Execute(ctx => doWork(ctx), new Context(), CancellationToken.None);","handlingStrategy":"fallback","validationCode":"// Either supply a retryAfterFactory or wrap in retry\nvar rl = Policy.RateLimit<TResult>(10, TimeSpan.FromSeconds(1), 10,\n    (retryAfter, ctx) => /* fallback value */ default);\nrl.Execute(func, context, ct);","typeGuard":null,"tryCatchPattern":"try { rl.Execute(func, context, ct); }\ncatch (RateLimitRejectedException ex) {\n    Thread.Sleep(ex.RetryAfter.Add(TimeSpan.FromMilliseconds(50)));\n    // retry or fall back\n}","preventionTips":["Wrap RateLimit in a Retry policy that backs off using exception.RetryAfter.","Consider a retryAfterFactory to convert rejection into a fallback TResult.","Tune numberOfExecutions and maxBurst from measured load.","Instrument RateLimitRejectedException rates to detect under-provisioned limits."],"tags":["ratelimit","rejection","throttle","retryafter","legacy-api"],"backgroundTag":null,"analyzedSha":"d0e46bdb1ee11ea50d0e4b6846d2633d6bc09bac","analyzedAt":"2026-08-13T16:36:01.959Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}