If you are building a .NET application that calls external APIs, you may eventually face a common problem:
"The API is getting too many requests. Slow down!"
Many third-party APIs have rate limits. For example, an API might allow only
10 requests per second.
If your application sends 50 requests per second, the API may start returning errors such as:
429 Too Many Requests
This is where rate limiting with Polly can help.
What Is Rate Limiting?
Rate limiting simply means:
Control how many requests your application sends within a certain amount of time.
For example:
Maximum: 5 requests per second
If your application needs to make 20 requests, instead of sending all 20 immediately, the rate limiter controls the requests.
Without rate limiting:
Request 1 ----\
Request 2 -----\
Request 3 ------> External API
Request 4 -----/
Request 5 ----/
With rate limiting:
Request 1 ---> API
Request 2 ---> API
Request 3 ---> API
Request 4 ---> Wait
Request 5 ---> Wait
The goal is to avoid overwhelming the external service.
What Is Polly?
Polly is a .NET resilience library.
It provides mechanisms for handling problems such as:
- Rate limiting
- Retries
- Timeouts
- Circuit breakers
- Fallbacks
- Other resilience scenarios
The important thing to understand is:
Polly doesn't make the external API faster. It helps your application behave better when communicating with external systems.
A Simple Real-World Example
Imagine your application calls a payment API.
The payment provider says:
Maximum: 10 requests per second
But your application receives 100 orders at the same time.
If you immediately send all 100 requests:
.NET Application
|
+---- Request 1
+---- Request 2
+---- Request 3
...
+---- Request 100
|
v
Payment API
The payment API may reject many requests.
With rate limiting:
.NET Application
|
v
Polly Rate Limiter
|
+---- Request 1
+---- Request 2
+---- Request 3
...
|
v
Payment API
Polly controls the flow.
Installing Polly
For modern .NET applications using HttpClient, install the Polly HTTP resilience integration:
# Adds Polly-based resilience support for HttpClient.
dotnet add package Microsoft.Extensions.Http.Resilience
This package integrates resilience strategies with IHttpClientFactory.
You can also use Polly directly when you need more control.
The Basic Idea
Let's say we want to allow only 5 requests every second.
Conceptually:
5 requests
|
v
1 second
If more requests arrive, they must wait or be rejected depending on the rate-limiting strategy.
Using Polly Rate Limiting with HttpClient
A common approach in an ASP.NET Core application is to configure a named or typed
HttpClient.
For example:
using Polly;
using Polly.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
// Register an HttpClient with Polly resilience.
builder.Services
.AddHttpClient("ExternalApi")
.AddResilienceHandler("rate-limiter", pipeline =>
{
// Add a rate limiter to the pipeline.
pipeline.AddRateLimiter(new HttpRateLimiterStrategyOptions
{
// Allow 5 requests in the configured window.
PermitLimit = 5,
// Time window for the limit.
Window = TimeSpan.FromSeconds(1)
});
});
var app = builder.Build();
app.Run();
The exact API available can vary with the Polly and Microsoft.Extensions.Http.Resilience package versions, so always check the version-specific documentation when implementing this in a production project.
Let's Understand the Code
The important part is:
PermitLimit = 5
This means we are limiting the number of permits available.
And:
Window = TimeSpan.FromSeconds(1)
means the limit is applied over a one-second window.
So conceptually:
5 requests
within
1 second
Using the HttpClient
Now we can use the registered client.
For example:
app.MapGet("/products", async (IHttpClientFactory factory) =>
{
// Get the HttpClient that we configured earlier.
var client = factory.CreateClient("ExternalApi");
// Call the external API.
var response = await client.GetAsync(
"https://api.example.com/products");
// Return the response status.
return response.StatusCode.ToString();
});
The important thing is that the request goes through the configured resilience pipeline.
Your API
|
v
HttpClient
|
v
Polly Rate Limiter
|
v
External API
What Happens When the Limit Is Reached?
Suppose the limit is:
5 requests / second
And your application tries to send 10 requests.
The first five can acquire permits.
The remaining requests depend on how your rate limiter is configured.
They may:
- Wait for a permit
- Be rejected
- Fail with a rate-limit-related exception
This is important because rate limiting is not the same thing as retrying.
Rate Limiting vs Retry
These two concepts are easy to confuse.
Rate limiting
Rate limiting controls:
How quickly should I send requests?
Example:
Maximum 5 requests per second
Retry
Retry means:
The request failed. Should I try again?
For example:
Request
|
v
API
|
v
503 Service Unavailable
|
v
Wait
|
v
Retry
They solve different problems.
Why Combine Rate Limiting and Retry?
Imagine the external API has a rate limit.
Your application sends too many requests and receives:
429 Too Many Requests
A retry strategy can retry the request later.
But blindly retrying can make the problem worse:
Request
|
v
429
|
v
Retry immediately
|
v
429
|
v
Retry immediately
|
v
429
Now your application is sending even more requests.
A better approach is to use rate limiting and intelligent retry behavior together.
Example: Rate Limiter + Retry
Conceptually, a resilience pipeline can contain multiple strategies:
pipeline.AddRateLimiter(...);
pipeline.AddRetry(...);
pipeline.AddTimeout(...);
Think of it as:
Application
|
v
Rate Limiter
|
v
Retry
|
v
Timeout
|
v
External API
Each strategy handles a different problem.
A Simple Retry Example
For example:
pipeline.AddRetry(new HttpRetryStrategyOptions
{
// Try the request up to 3 times.
MaxRetryAttempts = 3,
// Wait between retries.
Delay = TimeSpan.FromSeconds(1)
});
The idea is:
First attempt
|
fails
|
wait
|
Second attempt
|
fails
|
wait
|
Third attempt
In real applications, retry configuration should be based on which errors are safe to retry.
For example, retrying a GET request is generally very different from blindly retrying a payment operation.
A Better Beginner Architecture
For an external API, you might have:
Your .NET App
|
v
HttpClient
|
v
Polly Pipeline
|
+---------+---------+
| | |
v v v
Rate Limit Retry Timeout
| | |
+---------+---------+
|
v
External API
This is a simple way to think about resilience.
What If I Don't Want Requests to Wait?
Sometimes you don't want requests to wait for a permit.
For example, imagine a high-traffic application where waiting requests could consume too many resources.
In that case, you can configure the rate limiter to reject requests when the limit is reached.
The application can then return an appropriate response.
For example:
Request
|
v
Rate Limiter
|
+---- Permit available ---> External API
|
+---- No permit ----------> Reject
The exact rejection handling depends on how your pipeline and application are configured.
Rate Limiting Is Not Only for External APIs
You can use rate limiting in several scenarios.
For example:
External API
Your Application ---> Third-party API
Internal API
Service A ---> Service B
Expensive operation
Application ---> Expensive database operation
Login endpoint
You may also want to limit repeated requests to sensitive endpoints to reduce abuse.
However, application-level and infrastructure-level rate limiting solve different problems, so choose the right layer for your use case.
Polly Rate Limiting vs ASP.NET Core Rate Limiting
This is another important distinction.
ASP.NET Core has its own rate-limiting middleware.
It is commonly used when you want to control incoming requests to your application.
For example:
Client
|
v
Your ASP.NET Core API
|
v
Rate Limiter
|
v
Controller
Polly is especially useful when you're building a resilient outgoing HTTP client.
For example:
Your ASP.NET Core API
|
v
HttpClient
|
v
Polly
|
v
External API
So ask yourself:
"Am I limiting requests coming into my API, or controlling requests going out to another service?"
That answer will often tell you which approach you need.
A Beginner Example
Let's imagine a weather application.
Your application calls:
Weather API
The provider allows:
10 requests per second
You configure your client with a rate limiter.
builder.Services
.AddHttpClient("WeatherApi", client =>
{
// Set the base address of the external API.
client.BaseAddress =
new Uri("https://api.example.com/");
})
.AddResilienceHandler("weather-resilience", pipeline =>
{
// Limit outgoing requests.
pipeline.AddRateLimiter(
new HttpRateLimiterStrategyOptions
{
// Maximum number of requests allowed.
PermitLimit = 10,
// Apply the limit over one second.
Window = TimeSpan.FromSeconds(1)
});
});
Then use it:
app.MapGet("/weather", async (
IHttpClientFactory factory) =>
{
// Get the configured Weather API client.
var client = factory.CreateClient("WeatherApi");
// Make the external API request.
var response = await client.GetAsync("weather");
// Return the external response.
return Results.StatusCode(
(int)response.StatusCode);
});
The flow is:
User
|
v
Your API
|
v
Weather HttpClient
|
v
Polly Rate Limiter
|
v
Weather API
Important: Rate Limiting Doesn't Mean "5 Requests Total"
If you configure:
PermitLimit = 5
that does not necessarily mean your application can make only five requests in its entire lifetime.
It means the limiter allows a certain number of permits according to the configured rate-limiting algorithm and window.
For example:
5 requests
|
+---- 1 second ----+
|
v
New permits
The exact behavior depends on the rate-limiter algorithm you choose.
Common Beginner Mistakes
1. Confusing Rate Limiting with Retry
They are different.
Rate Limiting = Control request speed
Retry = Try a failed request again
2. Retrying 429 Immediately
If an API says:
429 Too Many Requests
- immediately retrying repeatedly can make the situation worse.
- Respect the API's rate-limit information and use sensible retry delays.
3. Limiting Incoming Requests with the Wrong Tool
- If your goal is to protect your ASP.NET Core API from too many incoming requests, look at the built-in ASP.NET Core rate-limiting middleware.
- If your goal is to control calls from your application to another API, Polly's HTTP resilience pipeline can be a good fit.
4. Forgetting Timeouts
- A rate limiter does not protect you from an external API that simply hangs.
- Consider a timeout as part of your resilience strategy.
Rate Limiter
+
Retry
+
Timeout
When Should You Use Polly Rate Limiting?
Polly rate limiting is a good choice when:
- Your application calls third-party APIs.
- The third-party API has request limits.
- You want centralized resilience policies for
HttpClient. - You need rate limiting together with retry and timeout behavior.
- You want to prevent your application from overwhelming another service.
Final Takeaway
If you're a beginner, remember this simple idea:
Polly rate limiting controls how quickly your .NET application sends requests to another service.
The basic flow is:
.NET Application
|
v
HttpClient
|
v
Polly Pipeline
|
v
Rate Limiter
|
v
External API
And remember the difference:
Rate Limiting
"Don't send too many requests."
Retry
"The request failed; try again."
Timeout
"Don't wait forever."
Circuit Breaker
"Stop calling a service that is consistently failing."
These strategies can be combined to build a much more resilient .NET application.
For a beginner, start with rate limiting + timeout, understand how they behave, and then add retry or circuit breaker behavior based on your application's actual needs.