Skip to content
v0.3.2

smartratelimit

Your API's rate limits, read from its own headers and respected automatically โ€” no 429s, no hand-rolled sleep loops

pip install smartratelimit

What is smartratelimit?

Every API tells you its rate limit on every response โ€” X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset โ€” and almost no client reads them. So you either guess a time.sleep(), or you find out the hard way with a 429.

smartratelimit reads those headers for you. Swap requests.get(url) for limiter.request('GET', url) and the limiter learns the limit from the first response, refills a token bucket at exactly that rate, and blocks the calling thread just long enough when you're about to run out. Nothing to configure โ€” most APIs are handled by the headers they already send.

When you need more than one process to share those limits โ€” Gunicorn workers, Celery tasks, a fleet of scrapers โ€” point it at SQLite or Redis and the token bucket becomes shared state.

Start here: 60-second quickstart ยท How it works ยท Which storage backend?


What you get

๐Ÿ“ก
Automatic detection
Reads X-RateLimit-*, RateLimit-* and Retry-After headers, with built-in profiles for GitHub, Stripe, Twitter, OpenAI and RapidAPI. Non-standard header names take a three-line map.
Explore Detection โ†’
๐Ÿชฃ
Token bucket pacing
The detected limit becomes a bucket that refills at limit / window per second. Requests are spread across the window instead of sprinting into the wall and stalling.
How it works โ†’
๐Ÿ’พ
Persistent state
Memory, SQLite, or Redis behind one storage= string. Restart your app and it still knows it has 12 requests left on this hour's quota.
Explore Storage โ†’
๐Ÿ”€
Multi-process safe
With Redis, every worker draws from the same bucket โ€” the limit is per API, not per process. Works with Gunicorn, Celery, and multi-machine deployments.
Compare backends โ†’
โšก
Async support
AsyncRateLimiter paces httpx and aiohttp calls with asyncio.sleep, so waiting on one endpoint never blocks the loop.
Explore Async โ†’
๐Ÿ”„
Retry strategies
Exponential, linear, or fixed backoff over any callable, sync or async, with a capped delay and a configurable list of retryable status codes.
Explore Retry โ†’
๐Ÿ“Š
Metrics
Count requests, 429s and utilization per endpoint, then export Prometheus text or JSON straight into your existing scrape target.
Explore Metrics โ†’
๐Ÿ–ฅ๏ธ
CLI
smartratelimit probe shows what an API advertises, status reads the stored quota, clear resets it โ€” useful against a shared SQLite or Redis backend.
Explore CLI โ†’

Sixty seconds

from smartratelimit import RateLimiter

limiter = RateLimiter()

# The first response teaches the limiter GitHub's limit; every call after
# that is paced to fit inside it.
for user in ["octocat", "torvalds", "gvanrossum"]:
    response = limiter.request("GET", f"https://api.github.com/users/{user}")
    print(response.json()["name"])

status = limiter.get_status("api.github.com")
print(f"{status.remaining}/{status.limit} left, resets in {status.reset_in:.0f}s")

Nothing above names a rate limit. GitHub's headers did.


When you don't need it

If you call an API a handful of times a day, a bare requests.get is fine. smartratelimit earns its place when you're looping over an API โ€” batch enrichment, scraping, fan-out jobs, sync workers โ€” where the limit is a real constraint and the failure mode is a 429 partway through a run.

It also assumes the limit belongs to you calling someone else. It is a client-side limiter, not a server-side one; it won't throttle inbound traffic to your own service.


Installation

pip install smartratelimit            # core, requests only
pip install smartratelimit[redis]     # + Redis storage backend
pip install smartratelimit[httpx]     # + httpx async client
pip install smartratelimit[aiohttp]   # + aiohttp async client
pip install smartratelimit[all]       # everything

Python 3.8+. The only hard dependency is requests.