Skip to content

Compared with others

headgate is a Swiss Army knife for HTTP API clients. Most libraries do one job well: send requests, retry, rate-limit, cache, break circuits, mock. headgate does all of those jobs in one base class, and makes them agree with each other. This page shows what that buys you, library by library, and when another tool is the better pick.

built in with another library, or code you write not available

HTTP clients

requests, niquests, httpx and aiohttp send HTTP requests, and do it well. headgate runs on httpx2 (pydantic's fork of httpx) and adds everything a client for someone else's API needs on top. It can also send through curl_cffi when you need libcurl.

requests niquests httpx aiohttp headgate
Async
HTTP/2
Retries with backoff
Waits for Retry-After
One deadline over every retry
Rate limiting that obeys the server
Circuit breaker
Caching
Pagination
Server-sent events
RFC 9457 problem details parsed
Every credential kept off other hosts
SSRF guard with DNS pinning
Response size cap
Ignores proxy variables and .netrc by default
Secrets redacted from error messages
Test fakes
Sync API

When a redirect leaves the origin, requests and httpx drop the Authorization header, and aiohttp drops Cookie and Proxy-Authorization too. headgate drops those and every other header and query parameter its auth declares, such as an X-API-Key, on every hop, next-page links included: see security.

The stack headgate replaces

Job What people usually add In headgate
Retries tenacity, stamina, backoff, httpx-retries, aiohttp-retry RetryPolicy
Rate limits aiolimiter, pyrate-limiter, limits RateBudget
Caching hishel, requests-cache, aiohttp-client-cache, CacheControl HttpCache, on hishel
Circuit breaking pybreaker, aiobreaker, purgatory, circuitbreaker BreakerPolicy
Server-sent events httpx-sse, aiohttp-sse-client stream().aiter_events()
Pagination a loop you write per endpoint paginate()
Test doubles respx, pytest-httpx, aioresponses, responses headgate.testing; vcrpy cassettes work too
Telemetry OpenTelemetry instrumentation packages typed events and an OpenTelemetry observer

Fewer dependencies is the small win. The big one is that the layers know about each other, which separate libraries cannot:

  • A cache hit is never retried, and spends no rate turn, because the cache sits below the retry layer and above the rate budget.
  • The retry layer and the rate budget read Retry-After the same way, so the retry's wait and the budget's pause end together instead of stacking.
  • One deadline bounds everything: every attempt, every backoff, every rate-budget wait and the body read.
  • A breaker counts only the host's own failures: a 404 your code asked for, or a call you cancelled, does not open it.

Library by library

tenacity stamina backoff urllib3 Retry headgate
Retries any code, not just HTTP
Async
Knows which HTTP requests are safe to re-send
Waits for Retry-After
Total time limit
Retry budget shared across calls
Idempotency keys for safe POST retries
Hedged requests
Instant retries in tests

Pick stamina or tenacity to retry something that is not an HTTP call, such as a database query. stamina's on= hook (25.2 and later) and tenacity's wait_exception can honor Retry-After with a little code. backoff's repository is archived, and it has not been released since 2022.

aiolimiter pyrate-limiter headgate
Async
Several rates at once
Shared across processes (Redis, SQLite)
Pauses when the server says 429 or 503
Reads RateLimit and X-RateLimit-* headers
Slows down on pushback (adaptive)
Separate lanes per path
Cap on requests in flight

Pick pyrate-limiter when many processes or machines must share one quota.

hishel requests-cache aiohttp-client-cache headgate
Async
Follows Cache-Control (RFC 9111)
Your own rules for APIs that send no cache headers
Entries kept apart per credential
Stores only answers your code accepted

headgate's cache is hishel, configured with your rules and wrapped so it is safe to share between credentials. Pick hishel directly when you want the provider's Cache-Control to decide; it supports httpx2 too.

pybreaker aiobreaker purgatory headgate
Async
One breaker per host, automatically
Counts only the host's own failures
Retries stop while it is open
State shared across processes (Redis)

Pick pybreaker or purgatory to guard something that is not HTTP, or to share breaker state across machines.

respx pytest-httpx aioresponses vcrpy headgate.testing
Canned responses
Record and replay real traffic
Failure schedules (fail twice, then succeed)
A fake clock, so retries and rate waits are instant
Breaker state isolated per test

respx and pytest-httpx patch httpx, not httpx2, so they do not see a headgate client's requests. headgate.testing is built for it, and vcrpy 8.3 and later record httpx2; headgate ships filters that keep credentials out of the cassettes.

What it is How headgate differs
uplink declare endpoints with decorators on an interface headgate endpoints are ordinary methods, and resilience is built in rather than added per decorator
openapi-python-client generates a typed client from an OpenAPI document use it when you have a spec; headgate is for APIs without one, or where you want control
Stainless SDKs (openai, anthropic) generated SDKs with retries and timeouts built in headgate brings that quality to a client you write yourself, and understands the same retry-after-ms and x-should-retry headers

When to use something else

  • Retrying code that is not an HTTP call (a database query, a file operation): use stamina, or tenacity when you need its full flexibility.
  • Python before 3.14: headgate needs 3.14 for its template-string paths. Use httpx with stamina, and add rate limiting and caching yourself.
  • Upstream httpx: headgate runs on httpx2 and refuses httpx objects, to catch the mix-up early. If your code has to stay on httpx, headgate is not for you yet.
  • A synchronous codebase: headgate is async only. requests or httpx's sync client is the simpler choice.
  • One or two calls in a script: a plain httpx2 call is fine. headgate pays off when a client has several endpoints, several consumers, or has to survive a provider's bad day.
  • A quota shared across machines: headgate's rate budget and breakers live in one process. Use pyrate-limiter or purgatory with Redis for that.

Why httpx2

headgate is built and tested on httpx2, pydantic's fork of httpx, and on nothing else. Its API is httpx's, so moving a client over is mostly an import change: import httpx2 where you had import httpx, and the same for the exceptions you catch. headgate refuses an httpx object anywhere it takes an httpx2 one, with a TypeError naming the mistake, rather than failing later in a confusing way.