RateLimitHeaders.Polly 1.0.0

dotnet add package RateLimitHeaders.Polly --version 1.0.0
                    
NuGet\Install-Package RateLimitHeaders.Polly -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RateLimitHeaders.Polly" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RateLimitHeaders.Polly" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="RateLimitHeaders.Polly" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add RateLimitHeaders.Polly --version 1.0.0
                    
#r "nuget: RateLimitHeaders.Polly, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package RateLimitHeaders.Polly@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=RateLimitHeaders.Polly&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=RateLimitHeaders.Polly&version=1.0.0
                    
Install as a Cake Tool

RateLimitHeaders

A .NET library for parsing IETF RateLimit headers and enabling proactive rate limit awareness in HTTP clients.

NuGet Build Status

Features

  • IETF Standard Parsing: Parses RateLimit and RateLimit-Policy headers per draft-ietf-httpapi-ratelimit-headers-10
  • Proactive Throttling: Delays requests before hitting 429 errors when quota is low
  • Polly Integration: Works seamlessly with Polly v8 resilience pipelines
  • DelegatingHandler: Drop-in handler for IHttpClientFactory
  • Extensible: Pluggable throttling algorithms for custom strategies
  • Observable: Callbacks for rate limit info, quota warnings, and throttling events

Installation

# Core library (IHttpClientFactory integration)
dotnet add package RateLimitHeaders

# For Polly v8 resilience pipeline integration
dotnet add package RateLimitHeaders.Polly

Quick Start

Using with IHttpClientFactory

services.AddHttpClient("MyApi")
    .AddRateLimitAwareHandler(options =>
    {
        options.EnableProactiveThrottling = true;
        options.QuotaLowThreshold = 0.1; // Warn at 10% remaining
        options.OnQuotaLow = args =>
        {
            logger.LogWarning("Quota low: {Remaining}/{Quota}",
                args.RateLimitInfo.Remaining,
                args.RateLimitInfo.Quota);
            return ValueTask.CompletedTask;
        };
    });
Handler Placement Order

When using multiple delegating handlers, place the rate limit aware handler:

  • AFTER authentication handlers (so auth headers are present for state tracking)
  • BEFORE retry/resilience handlers (so rate limit info is available for retry decisions)
services.AddHttpClient("MyApi")
    .AddHttpMessageHandler<AuthenticationHandler>()  // 1. Auth first
    .AddRateLimitAwareHandler()                      // 2. Rate limiting
    .AddStandardResilienceHandler();                 // 3. Retry/resilience last

Using with Polly Resilience Pipelines

var pipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddRateLimitHeaders(options =>
    {
        options.EnableProactiveThrottling = true;
        options.OnRateLimitInfo = args =>
        {
            logger.LogDebug("Rate limit: {Info}", args.RateLimitInfo);
            return ValueTask.CompletedTask;
        };
    })
    .AddRetry(new RetryStrategyOptions<HttpResponseMessage>())
    .Build();

// Create a context to capture rate limit info
var context = ResilienceContextPool.Shared.Get(cancellationToken);

try
{
    var response = await pipeline.ExecuteAsync(
        async (ctx, ct) => await httpClient.GetAsync("/api/resource", ct),
        context,
        cancellationToken);

    // Access parsed rate limit info from context
    if (context.Properties.TryGetValue(RateLimitContextProperties.RateLimitInfoKey, out var info))
    {
        Console.WriteLine($"Remaining: {info.Remaining}/{info.Quota}");
    }
}
finally
{
    ResilienceContextPool.Shared.Return(context);
}

Accessing Rate Limit Info

Use the extension methods on HttpResponseMessage:

var response = await httpClient.GetAsync("/api/resource");

// Parse headers directly
if (response.TryGetRateLimitInfo(out var info))
{
    Console.WriteLine($"Remaining: {info.Remaining}/{info.Quota}");
    Console.WriteLine($"Resets in: {info.ResetSeconds}s");

    if (info.IsQuotaLow(0.1))
        Console.WriteLine("Warning: Quota is low!");
}

// Or get cached info when using RateLimitAwareHandler
if (response.TryGetStoredRateLimitInfo(out var cached))
{
    Console.WriteLine($"Cached: {cached.Remaining}/{cached.Quota}");
}

IETF RateLimit Headers

This library parses the IETF standard format:

RateLimit: "default";r=50;t=30
RateLimit-Policy: "default";q=100;w=60
Header Format Description
RateLimit "policy";r=remaining;t=reset Current rate limit state
RateLimit-Policy "policy";q=quota;w=window Rate limit policy definition

Parsed Values

Property Description
PolicyName The rate limit policy name (e.g., "default", "api-v2")
Remaining Requests remaining in the current window
ResetSeconds Seconds until the window resets
Quota Maximum requests allowed per window
WindowSeconds Duration of the rate limit window
IsValid Whether at least one header was successfully parsed

Proactive Throttling

When enabled, the handler automatically delays requests when quota is low:

// Default algorithm: PercentageThrottlingAlgorithm
// Starts throttling when remaining < 10% of quota
// Delay = (threshold - remainingPct) * resetSeconds * factor
// Max delay = 5 seconds

options.ThrottlingAlgorithm = new PercentageThrottlingAlgorithm(
    threshold: 0.1,    // Start at 10% remaining
    factor: 1.0,       // Delay multiplier
    maxDelay: TimeSpan.FromSeconds(5));

Custom Throttling Algorithm

Implement IThrottlingAlgorithm for custom strategies:

public class CustomThrottlingAlgorithm : IThrottlingAlgorithm
{
    public ThrottlingResult Evaluate(RateLimitInfo info)
    {
        if (!info.IsValid || info.Remaining > 5)
            return ThrottlingResult.NoThrottle;

        var delay = TimeSpan.FromSeconds(info.ResetSeconds / 2.0);
        return ThrottlingResult.Throttle(delay, "Custom throttle");
    }
}

Event Callbacks

OnRateLimitInfo

Called whenever rate limit headers are parsed:

options.OnRateLimitInfo = args =>
{
    metrics.RecordRateLimit(
        args.RateLimitInfo.PolicyName,
        args.RateLimitInfo.Remaining,
        args.RateLimitInfo.Quota);
    return ValueTask.CompletedTask;
};

OnQuotaLow

Called when remaining quota falls below threshold:

options.OnQuotaLow = args =>
{
    alertService.SendAlert(
        $"API quota at {args.RemainingPercentage:P0}",
        args.RequestUri);
    return ValueTask.CompletedTask;
};

OnThrottling

Called before a request is throttled:

options.OnThrottling = args =>
{
    logger.LogInformation(
        "Throttling request to {Uri} for {Delay}ms: {Reason}",
        args.RequestUri,
        args.Delay.TotalMilliseconds,
        args.Reason);
    return ValueTask.CompletedTask;
};

Per-Endpoint State Tracking

By default, rate limit state is tracked per hostname:

// Default: hostname
// api.example.com/v1/users -> "api.example.com"
// api.example.com/v1/orders -> "api.example.com"

// Custom key extraction (e.g., include path segment):
options.StateKeyExtractor = request =>
{
    var uri = request.RequestUri;
    if (uri is null) return "default";
    var segments = uri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries);
    return segments.Length > 0 ? $"{uri.Host}/{segments[0]}" : uri.Host;
};

Partition Key Considerations

Some APIs partition rate limits by tenant, user, or API key (indicated by the pk parameter in response headers). The library parses this value into RateLimitInfo.PartitionKey, but proactive throttling does not automatically use it because:

  1. The state key must be computed before sending the request (for proactive throttling)
  2. The partition key is only known after receiving the response

If your API uses partition-based rate limiting, use StateKeyExtractor to include the partition identifier from your request:

// For APIs that partition by API key header:
options.StateKeyExtractor = request =>
{
    var apiKey = request.Headers.TryGetValues("X-API-Key", out var values)
        ? values.FirstOrDefault()
        : null;
    var host = request.RequestUri?.Host ?? "default";
    return apiKey is not null ? $"{host}:{apiKey}" : host;
};

// For APIs that partition by tenant ID in the path:
options.StateKeyExtractor = request =>
{
    var uri = request.RequestUri;
    if (uri is null) return "default";
    // Extract tenant from path like /api/tenants/{tenantId}/...
    var match = Regex.Match(uri.AbsolutePath, @"/tenants/([^/]+)");
    return match.Success ? $"{uri.Host}:{match.Groups[1].Value}" : uri.Host;
};

This ensures each partition is tracked independently for accurate proactive throttling.

Compatibility

Note: This library requires .NET 8.0 or later. Earlier framework versions are not supported.

Framework Supported
.NET 8.0 Yes
.NET 9.0 Yes
.NET 10.0 Yes

Dependencies

RateLimitHeaders (core):

RateLimitHeaders.Polly (additional):

Thread Safety

The library is designed to be thread-safe:

  • RateLimitInfo is a readonly struct (immutable)
  • State tracking uses ConcurrentDictionary
  • Handlers are transient (as required by IHttpClientFactory)

License

MIT License - see LICENSE for details.

Contributing

Contributions are welcome! Please read our contributing guidelines and submit PRs to the develop branch.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on RateLimitHeaders.Polly:

Package Downloads
Cloudflare.NET.Api

Cloudflare.NET - A comprehensive C# client library for the Cloudflare REST API. Manage DNS records, Zones, R2 buckets, Workers, WAF rules, Turnstile, and security features with strongly-typed .NET code.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 8,706 12/20/2025