Skip to content

How-To Combine Rate Limiting with Automatic Retries?

Even with a rate limiter, you may occasionally receive 429 Too Many Requests HTTP errors if the server's capacity fluctuates or if you have multiple clients running. A local rate limiter is your first line of defense, but it cannot guarantee that you won't reach a server-side limit. The solution is to combine rate limiting with automatic retries.

When you combine both techniques, it makes sense for the inner layer to be the rate limiter, while the outer layer handles retries. This way, each retry attempt is also subject to rate limiting.

Using httpx-tenacity

For this example, we use the companion package httpx-tenacity which provides a clean HTTPX transport-based approach to retrying. It also comes with sensible defaults that retry on server-side errors.

import httpx
from httpx_limiter import AsyncRateLimitedTransport, Rate
from httpx_limiter.aiolimiter import AiolimiterAsyncLimiter
from httpx_tenacity import AsyncTenaciousTransport


async def main() -> None:
    retry = AsyncTenaciousTransport.create(
        max_attempts=3,
        min_wait_seconds=0.1,
        max_wait_seconds=0.5,
    )
    retry.transport = AsyncRateLimitedTransport.create(
        limiter=AiolimiterAsyncLimiter.create(Rate.create(magnitude=10))
    )
    async with httpx.AsyncClient(transport=retry) as client:
        response = await client.get("https://api.example.com/data")
        print(response.json())