REST API rate-limit headers: from X-RateLimit-* to the IETF standard
A practical guide to REST API rate-limit headers: X-RateLimit-*, the IETF RateLimit draft fields, 429/Retry-After, and how to design and consume them.
Image used for representation purposes only.
Why rate-limit headers matter
Rate limiting is how REST APIs stay reliable under load: servers cap how fast a client can make requests, and clients adapt to avoid throttling and outages. The contract between the two lives in HTTP response headers. Historically, each API invented its own header names and formats; today, an IETF effort is standardizing a common set so clients can interoperate without per‑API parsing hacks. As of September 1, 2026, that standard is still an Internet‑Draft, but it’s mature and widely referenced. (datatracker.ietf.org )
Two worlds: de facto vs. emerging standard
There are effectively two ecosystems you’ll meet in the wild:
- The legacy/de facto trio used by many public APIs: X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. GitHub, for example, returns these headers on most REST responses; reset is expressed as UTC epoch seconds. X (formerly Twitter) also exposes similar counters and an epoch-based reset. (docs.github.com )
- The IETF “RateLimit header fields for HTTP” draft, which defines two structured fields: RateLimit-Policy (the policy) and RateLimit (the live counters). These are designed to replace ad‑hoc headers and make semantics unambiguous. (datatracker.ietf.org )
Note: “X‑” prefixes for custom headers were deprecated years ago, but they remain common in rate limiting, so clients must still handle them. (developer.mozilla.org )
The de facto pattern (X‑RateLimit‑*)
Most REST APIs that predate the draft use this contract:
- X-RateLimit-Limit: total requests allowed per window.
- X-RateLimit-Remaining: how many requests remain in the current window.
- X-RateLimit-Reset: when the window resets (commonly epoch seconds), sometimes a delta in seconds; check each API’s docs. (docs.github.com )
Example (GitHub style):
HTTP/1.1 200 OK
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4987
X-RateLimit-Reset: 1725206400 ; UTC epoch seconds
Practical pitfalls you’ll encounter:
- Units vary for reset (epoch seconds vs. relative seconds). Treat values as strings and parse per provider guidance. (api-rate-limiting.com )
- Casing varies (x-ratelimit-remaining vs. X-RateLimit-Remaining). Header names are case‑insensitive, but normalize them in your client. (datatracker.ietf.org )
The emerging standard: RateLimit-Policy and RateLimit
The IETF draft defines two complementary response fields using HTTP Structured Fields syntax:
- RateLimit-Policy: a relatively stable description of one or more quota policies. Parameters include q (quota units) and w (time window, seconds). Optional pk conveys the partition key (e.g., per user or token). Example:
RateLimit-Policy: "burst";q=100;w=60, "daily";q=1000;w=86400. (datatracker.ietf.org ) - RateLimit: live counters that may change on every response. Required parameter r conveys available quota units; optional t conveys the effective window in seconds; optional pk mirrors the partition key. Example:
RateLimit: "burst";r=42;t=18. (datatracker.ietf.org )
Putting them together:
HTTP/1.1 200 OK
RateLimit-Policy: "basic";q=100;w=60
RateLimit: "basic";r=60;t=58
Content-Type: application/json
This tells the client: policy “basic” allows 100 units per 60 seconds; 60 units remain, and you should consider a ~58s effective window for your pacing logic. The draft explicitly allows returning these fields on successful and throttled responses. (datatracker.ietf.org )
Relationship to HTTP 429 and Retry-After
When a client exceeds limits, servers typically respond with HTTP 429 Too Many Requests and may include Retry-After to indicate how long to wait before retrying; this status was standardized in RFC 6585, and Retry-After semantics are specified in HTTP Semantics. The draft shows examples that combine 429, Retry-After, and RateLimit fields. (rfc-editor.org )
Example throttle response:
HTTP/1.1 429 Too Many Requests
Retry-After: 20
RateLimit-Policy: "dynamic";q=100;w=60
RateLimit: "dynamic";r=0;t=20
Content-Type: application/problem+json
Designing your API: which headers should you ship?
If you’re publishing a new REST API in 2026, a pragmatic approach is:
- Emit the standard RateLimit-Policy and RateLimit fields.
- For backward compatibility, also emit X-RateLimit-* for at least one API version so existing SDKs continue to work.
- Keep representations consistent: if you use epoch seconds in legacy fields, do not silently switch to relative seconds mid‑version. (api-rate-limiting.com )
- Include Retry-After with 429 to provide an explicit backoff hint, even when returning RateLimit fields. (developer.mozilla.org )
CORS tip: if your API is called from browsers, expose these non‑simple headers so JavaScript can read them:
Access-Control-Expose-Headers: RateLimit, RateLimit-Policy, Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
GitHub demonstrates this pattern for its own headers. (github.com )
Client implementation guidelines
A robust client should:
- Parse both ecosystems
- Prefer RateLimit/RateLimit-Policy when present; otherwise, fall back to X-RateLimit-*. Expect variant casing. (datatracker.ietf.org )
- Handle reset semantics precisely
- For legacy X-RateLimit-Reset, check docs for units (epoch vs. delta). GitHub uses epoch seconds; X’s docs and SDKs commonly use epoch‑based reset. (docs.github.com )
- Respect Retry-After
- When 429 arrives, delay at least the Retry-After duration before retry. If Retry-After is an HTTP‑date, compute a delta; if it’s an integer, treat it as seconds. (developer.mozilla.org )
- Pace using the live counters
- With RateLimit: use r (remaining) and t (window) to schedule requests over the effective window, applying jitter to avoid thundering herds.
- Support multiple policies
- Servers can send multiple policies (e.g., hourly and daily). Choose the tightest current constraint (smallest r/t) as your cap. (datatracker.ietf.org )
- Expect partial or missing fields
- Servers may omit fields for performance, or intermediaries may strip them. Fail soft: assume conservative limits until headers reappear. (datatracker.ietf.org )
Example: minimal JavaScript pacing
async function fetchWithRateLimit(url, opts = {}) {
const res = await fetch(url, opts);
// Prefer standard fields
const rl = res.headers.get('ratelimit');
const rlp = res.headers.get('ratelimit-policy');
// Fallback to legacy
const xRemain = res.headers.get('x-ratelimit-remaining');
const xReset = res.headers.get('x-ratelimit-reset');
if (res.status === 429) {
const retryAfter = res.headers.get('retry-after');
const waitMs = retryAfter && /\d+/.test(retryAfter)
? parseInt(retryAfter, 10) * 1000
: 30_000; // default backoff if date or missing
await new Promise(r => setTimeout(r, waitMs));
return fetchWithRateLimit(url, opts);
}
// Very small, illustrative parser for one-item RateLimit fields
if (rl) {
// Example: "basic";r=60;t=58
const m = rl.match(/r=(\d+).*t=(\d+)/);
if (m) {
const [_, r, t] = m;
const perReq = Math.max(1, Math.floor(parseInt(t, 10) / Math.max(1, parseInt(r, 10))));
await new Promise(r => setTimeout(r, perReq * 1000));
}
} else if (xRemain && xReset) {
// Epoch seconds until reset -> spread remaining across that time
const now = Math.floor(Date.now() / 1000);
const remain = Math.max(0, parseInt(xRemain, 10));
const seconds = Math.max(1, parseInt(xReset, 10) - now);
const perReq = Math.ceil(seconds / Math.max(1, remain));
await new Promise(r => setTimeout(r, perReq * 1000));
}
return res;
}
Mapping legacy to the draft
If your infrastructure already emits X-RateLimit-* and you want to add the draft fields, a straightforward mapping for a single fixed window is:
- RateLimit-Policy: set q to X-RateLimit-Limit and w to your window length (seconds).
- RateLimit: set r to X-RateLimit-Remaining and t to the remaining or effective seconds in the current window.
Example transformation on the same response:
# Legacy only
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 120
X-RateLimit-Reset: 1725207000
# With draft fields added
RateLimit-Policy: "permin";q=600;w=60
RateLimit: "permin";r=120;t=12
Multiple overlapping windows (e.g., per‑minute and per‑day) can be modeled by listing both in RateLimit-Policy and choosing which one to reflect in RateLimit (or returning multiple items). The draft provides multi‑policy examples. (datatracker.ietf.org )
Security, privacy, and intermediaries
- Partition keys (pk) identify how quotas are allocated (per user, token, IP). Avoid sensitive identifiers and consider impersonation risks. (datatracker.ietf.org )
- Intermediaries should not make the policy more permissive; origin services are authoritative for enforcement. (datatracker.ietf.org )
Testing and observability checklist
- Log the raw headers your API emits and what your client parsed.
- Validate units and formats for any reset/timeout hints.
- Verify CORS exposure for browser clients. (github.com )
- Exercise 429 paths and confirm Retry-After handling. (developer.mozilla.org )
Quick reference
- Use HTTP 429 for throttling; include Retry-After. (rfc-editor.org )
- Prefer RateLimit-Policy and RateLimit; continue X-RateLimit-* short‑term for compatibility. (datatracker.ietf.org )
- Document units and keep them stable within an API version. (api-rate-limiting.com )
- Expose headers to browsers via Access-Control-Expose-Headers. (github.com )
Final thoughts
The IETF draft brings long‑needed consistency: clear policy vs. live counters, explicit units, and support for multiple windows and partitioning. Even before it becomes an RFC, adopting these headers (alongside your legacy ones) lets standards‑aware clients do the right thing automatically—and keeps your API predictable under pressure. (datatracker.ietf.org )
Related Posts
Header-Based API Versioning: A Practical Guide
A practical guide to header-based API versioning: patterns, negotiation, caching, examples, and rollout strategies for evolving contracts without breaking clients.
REST API Endpoint Naming: Conventions That Scale
A practical guide to naming REST API endpoints: principles, patterns, examples, and a checklist for clear, consistent, and evolvable URLs.
REST API Conditional Requests: Mastering If-Modified-Since
A practical guide to HTTP If-Modified-Since for REST APIs: how it works, 304 responses, server/client examples, pitfalls, and best practices.