Built on the standards¶
HTTP already has answers to the hard questions an API client faces: which requests are safe to send twice, how long to wait when a server says "slow down", what an error body looks like, where the next page is. They are written down in RFCs. headgate reads them so you do not have to, and follows them by default.
-
17 specifications
IETF RFCs and drafts, the WHATWG event-stream format and Python's own PEP 750, each tied to the behavior it drives.
-
Polite by default
Retry-Afterwaited exactly, rate-limit headers obeyed,Idempotency-Keysent on writes. Providers see a well-behaved client. -
Strict where it matters
A malformed header is ignored, as each spec asks, rather than guessed at. Nothing unsafe is retried.
-
Honest about exceptions
The one place headgate chooses differently, the cache, is spelled out with the reason.
You do not need to know any of this to use headgate
Every row below is something headgate does for you. The page is here for the curious, for reviewers, and for anyone who has to justify a dependency.
Sending and retrying safely¶
| Standard | What it says | What headgate does |
|---|---|---|
| RFC 9110 §9.2.2 Idempotent methods | GET, HEAD, OPTIONS, PUT and DELETE can be repeated without extra effect | only these are retried after an ambiguous failure, and only these can be hedged |
| Idempotency-Key (IETF draft) | a client key lets a server recognize a re-sent POST or PATCH | IdempotencyKeys() stamps one key per call, so every retry carries the same one |
| RFC 8470 425 Too Early | the server did not process the request | retried for every method, POST included, since nothing happened |
RFC 9113 §8.7 HTTP/2 REFUSED_STREAM |
the server refused the stream before doing any work | retried for every method, for the same reason |
| RFC 9110 §15.4 Redirects | 303 becomes GET; 307 and 308 keep the method and body | followed as written, except a 307 or 308 that would re-send a one-shot body and any https-to-http hop |
Waiting when asked¶
| Standard | What it says | What headgate does |
|---|---|---|
RFC 9110 §10.2.3 Retry-After |
how long to wait before trying again, in seconds or as a date | waited exactly on a 429 or 503, up to a cap; past it the call fails at once and tells you when to come back |
| RFC 6585 §4 429 Too Many Requests | the client is over its rate limit | raised as RateLimitError, and pauses the shared budget so other calls stop too |
| RateLimit headers (IETF draft) | RateLimit and RateLimit-Policy say how much quota is left and when it returns |
an exhausted quota pauses the rate budget until it returns; other quota units are skipped |
| RFC 9651 Structured Field Values | the syntax the RateLimit headers are written in | parsed with the RFC's own limits; a malformed field is ignored, as the draft asks |
Reading responses¶
| Standard | What it says | What headgate does |
|---|---|---|
| RFC 9457 Problem Details | errors as application/problem+json with type, title, detail |
parsed into exc.problem; the message shows its title and detail instead of raw JSON |
| RFC 6839 Structured syntax suffixes | application/problem+json is a kind of JSON |
an Expect(accept="application/json") accepts it |
| RFC 9110 §13.1 Conditional requests | If-None-Match and If-Modified-Since let a server answer 304 Not Modified |
conditional() sends them and returns Fresh or NotModified |
| WHATWG Server-sent events | the text/event-stream format and Last-Event-ID resumption |
aiter_events() parses events as they arrive; last_event_id= resumes |
RFC 9745 Deprecation and RFC 8594 Sunset |
an endpoint announces it is deprecated or going away | a Deprecated event with the dates and links, once per call |
Pages and links¶
| Standard | What it says | What headgate does |
|---|---|---|
| RFC 8288 Web Linking | Link: <...>; rel="next" names the next page |
LinkHeader follows it; the parser handles quoted commas and repeated parameters as the RFC says |
| RFC 6454 Origins | an origin is a scheme, host and port | credentials are pinned to an origin, circuit breakers are kept per origin |
Addresses and paths¶
| Standard | What it says | What headgate does |
|---|---|---|
| RFC 6052 NAT64 | 64:ff9b::/96 carries an IPv4 address inside IPv6 |
the SSRF guard judges such an address by the IPv4 address it reaches |
| PEP 750 Template strings | Python 3.14's t"..." keeps values apart from the template |
each value in a path is percent-encoded as exactly one segment |
Caching: where headgate chooses differently¶
RFC 9111 says what a shared HTTP cache may store, based on headers such as
Cache-Control. headgate's cache is hishel's, which implements RFC 9111, but headgate configures it with
your rules (which paths, methods and statuses, for how long) instead of the provider's headers. That is on
purpose: many APIs send no cache headers, or no-store on data that is safe to keep for an hour. When you want the
origin to decide, use conditional reads, which follow RFC 9110 to the letter.
Widely used conventions¶
Not every useful convention is an RFC. headgate understands the common ones too.
| Header | Where it comes from | What headgate does |
|---|---|---|
retry-after-ms |
SDKs generated by Stainless, such as OpenAI's | a millisecond Retry-After; preferred when both are sent |
x-should-retry |
the same SDKs | the server's own word on whether a re-send is safe, followed by default |
X-RateLimit-Remaining, X-RateLimit-Reset |
GitHub, and many others | read when RateLimit is absent; the reset may be seconds or a Unix time |
x-request-id, request-id, x-amzn-requestid, x-ms-request-id, cf-ray |
most platforms | read into every error's request_id, for support tickets |