Skip to main content

ratelimit-headers

Zero-dependency HTTP rate-limit header parser — pure Python stdlib only.

Parses heterogeneous rate-limit header formats from GitHub, Stripe, Twitter/X, the IETF draft spec, and RFC 7231 into a single canonical RateLimitState dataclass.

# From PyPI (once published)
pip install ratelimit-headers

# From local clone
pip install .
from ratelimit_headers import parse

state = parse(response_headers={
    "X-RateLimit-Limit": "5000",
    "X-RateLimit-Remaining": "4999",
    "X-RateLimit-Reset": "1893456000",
})
print(state.limit)        # 5000
print(state.remaining)   # 4999
print(state.reset)       # datetime(2099, 12, 31, tzinfo=UTC)
print(state.source)      # "github"

⚡ Performance & Benchmarks

No competitors exist — this is the first pure-stdlib rate-limit header normaliser on PyPI.

python3 benchmarks/run_benchmark.py

Why ratelimit-headers?

Every major HTTP API uses a different rate-limit header convention. Existing PyPI packages (aiolimiter, async-rate-limit) are enforcement libraries, not parsing/normalisation utilities. No pure-stdlib package normalised these heterogeneous headers into a uniform struct.

ratelimit-headers is a single ~150 LOC library + CLI that handles:

Provider Header namespace Reset format
GitHub X-RateLimit-* Unix epoch
Stripe X-RateLimit-* Unix epoch
Twitter/X x-rate-limit-* Unix epoch
IETF draft RateLimit-* + Retry-After delta-seconds / HTTP-date
RFC 7231 Retry-After delta-seconds / HTTP-date

Key Features

  • Pure stdlib — zero third-party dependencies
  • Five parser formats — GitHub, Stripe, Twitter/X, IETF draft, RFC 7231
  • Canonical dataclass — RateLimitState with limit, remaining, reset, retry_after, window, source
  • CLI — parse, detect, wait subcommands
  • Never crashes — malformed/garbage input returns RateLimitState(source="unknown")
  • Type-safe — isinstance guards prevent AttributeError on non-str keys
  • Fuzz-tested — NaN, hex, oversized ints, non-UTF8 bytes all handled safely

API Reference

RateLimitState dataclass

from ratelimit_headers import RateLimitState

@dataclass(frozen=True, slots=True)
class RateLimitState:
    limit:        Optional[int]      # requests allowed
    remaining:    Optional[int]      # requests remaining
    reset:        Optional[datetime] # UTC reset time
    retry_after:  Optional[int]      # seconds until retry
    window:       Optional[int]      # window size in seconds
    source:       Optional[str]       # format name

parse(request_headers=None, response_headers=None) -> RateLimitState

Parses rate-limit headers from an HTTP response. Returns a RateLimitState. Never raises — malformed input yields RateLimitState(source="unknown").

from ratelimit_headers import parse

state = parse(response_headers=headers)

detect_format(headers: dict) -> str

Returns the detected format name: "github" | "stripe" | "twitter" | "ietf" | "retry-after" | "unknown".

format_name(source: str) -> str

Human-readable name for a format identifier.

CLI

# Parse headers from command-line arguments
ratelimit-headers parse "X-RateLimit-Limit: 100" "X-RateLimit-Remaining: 99"
ratelimit-headers parse --json "X-RateLimit-Limit: 100"

# Detect format from a file
ratelimit-headers detect --input headers.txt
ratelimit-headers detect --json < headers.txt

# Wait until rate-limit resets (reads from stdin)
ratelimit-headers wait --max 300 < headers.txt

CLI JSON output schema

{
  "limit": 5000,
  "remaining": 4999,
  "reset": "2099-12-31T23:59:59+00:00",
  "retry_after": null,
  "window": 3600,
  "source": "github"
}

Limitations

  • No enforcement — this library parses headers only; it does not throttle, back off, or retry
  • No async — synchronous only; no asyncio or aiohttp integration
  • No server-side component — not a middleware or reverse-proxy plugin
  • No retry orchestration — does not retry failed requests
  • Does not parse quoted-parameter syntax inside header values (e.g. Limit: 100; w=3600)

Known issues (fuzzing-discovered, non-blocking for v0.1.0)

The v0.1.0 release ships with two known minor issues discovered during cycle_51 adversarial fuzzing (benchmarks/adversarial/FUZZING_REPORT.md). Both are classified Medium non-blockers with trivial 1-LOC fixes queued for cycle_52:

  • F-10 — reset=0 epoch yields remaining=100 instead of 0. When the upstream server sends X-RateLimit-Reset: 0 (Unix epoch, already-elapsed window), parse() returns remaining=100 rather than 0 because the window-elapsed branch sets remaining = limit instead of 0. Trivial 1-LOC fix.
  • F-15 — parsers.retry_after propagates RuntimeError instead of returning RateLimitState(source="unknown"). All other parsers are wrapped in defensive except Exception, but parsers.retry_after lets internal RuntimeErrors escape. Trivial 1-LOC try/except wrap.
  • F-28 — coerce_int raises OverflowError on values >2^63. Treated as Info (not Medium) because the function is not on the public surface — it is reached only when callers bypass the documented parse() entry point with request_headers= for an unsupported scheme.
  • F-NEW-1 — parse_http_date(None) raises AttributeError. Same surface as F-28; non-public helper, defensive except Exception queued.

Non-Goals

  • NOT a rate limiter / throttler / enforcement library
  • NOT an HTTP client
  • NOT async
  • NOT a policy enforcer
  • NOT a server-side component

Test suite

pytest --collect-only -q | tail -1

155 tests across 9 test files — all passing.

Full adversarial fuzzing report: benchmarks/adversarial/FUZZING_REPORT.md.

License

MIT

Metadata

Release files for ratelimit-headers 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ratelimit-headers 0.1.0
File Size Uploaded
ratelimit_headers-0.1.0.tar.gz 364.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ratelimit-headers 0.1.0
File Interpreter ABI Platform
ratelimit_headers-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 377.8 kB

Release files / ratelimit_headers-0.1.0.tar.gz

Download URL ratelimit_headers-0.1.0.tar.gz
Size 364.1 kB
Tags Source
SHA-256 checksum
How to use checksums
addb344eb31411fec112a805794e0621da05ac1bc23cd66577a4616f82664ebc
BLAKE2b-256 checksum
How to use checksums
ee471d2b3321a781940fd6f9fb03a78f0cc638b63a3e3a4b2cb2cbe592cb16ea
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.15

Release files / ratelimit_headers-0.1.0-py3-none-any.whl

Download URL ratelimit_headers-0.1.0-py3-none-any.whl
Size 13.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1d375a1cda3d382a04f001c621e89dec276359102def142c2e27244dca28d93f
BLAKE2b-256 checksum
How to use checksums
d59cbb3dd4a21fd9a525f551613d56379a4e3e21e38c48a6895c14907c6a7a0c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.15

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page