# HTTP headers and 429 response The extension injects standard rate-limit headers on **every** request — both allowed and rejected. Clients can use these headers to implement back-off logic without waiting for a 429. ## Response headers | Header | Type | Description | |---|---|---| | `X-RateLimit-Limit` | integer | Maximum requests allowed in the current window | | `X-RateLimit-Remaining` | integer | Requests remaining in the current window (≥ 0) | | `X-RateLimit-Reset` | Unix timestamp (s) | When the quota resets or the next token arrives | | `Retry-After` | seconds | **Only on 429** — how long to wait before retrying (≥ 1) | ### Example — allowed request ``` HTTP/1.1 200 OK X-RateLimit-Limit: 100 X-RateLimit-Remaining: 73 X-RateLimit-Reset: 1711750860 Content-Type: application/json ``` ### Example — rejected request (429) ``` HTTP/1.1 429 Too Many Requests X-RateLimit-Limit: 100 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1711750860 Retry-After: 1 Content-Type: text/plain Too Many Requests ``` ## Header semantics by strategy ### FIXED_WINDOW | Header | Value | |---|---| | `X-RateLimit-Reset` | Unix timestamp of the **next window start** (aligned to clock) | | `Retry-After` | Seconds until `X-RateLimit-Reset` (minimum 1) | At a 1-second window boundary `Retry-After` will typically be `1`. ### TOKEN_BUCKET | Header | Value | |---|---| | `X-RateLimit-Remaining` | Current token count (may increase between requests due to refill) | | `X-RateLimit-Reset` | Estimated Unix timestamp when the **next token arrives** | | `Retry-After` | Milliseconds-precise estimate converted to seconds (minimum 1) | Because the token bucket refills continuously, `X-RateLimit-Reset` is a near-future timestamp rather than an aligned window boundary. ## Retry-After precision `Retry-After` is computed as: ``` retryAfter = max(1, X-RateLimit-Reset - currentTimeSeconds) ``` The minimum value is always `1` second — RFC 7231 discourages `Retry-After: 0` as it encourages instant retry loops. ## Client-side back-off example (Java) ```java HttpResponse res = client.send(request, BodyHandlers.ofString()); if (res.statusCode() == 429) { String retryAfter = res.headers().firstValue("Retry-After").orElse("1"); long waitMs = Long.parseLong(retryAfter) * 1000L; Thread.sleep(waitMs); // retry... } ``` ## Client-side back-off example (JavaScript fetch) ```js const res = await fetch('/api/search?q=flash'); if (res.status === 429) { const retryAfter = parseInt(res.headers.get('Retry-After') ?? '1', 10); await new Promise(r => setTimeout(r, retryAfter * 1000)); // retry... } ``` ## Monitoring / alerting `X-RateLimit-Remaining` can be scraped by a metrics agent to track approaching limits before they hit 429: - `remaining / limit < 0.1` → warning (less than 10% quota left) - `status == 429` → rate-limit violation counter increment If `flash-ext-limiter` is used together with a future metrics extension, the 429 rate per resolver key is a natural signal for abuse detection or auto-scaling. ## Header injection timing Headers are injected **before** calling `next.handle(req, res)` on allowed requests, and **instead of** calling it on rejected requests. This means: - Handlers cannot accidentally overwrite `X-RateLimit-*` headers (they are set first, but handlers that call `res.header(...)` with the same name will add a second value — avoid this by not setting these headers manually). - On 429, the handler body is never executed — no side effects occur. ## Integration with Swagger UI (flash-ext-openapi) When `flash-ext-openapi` is installed, handlers annotated with `@Limit` automatically contribute rate-limit response headers to generated OpenAPI responses: - `X-RateLimit-Limit` - `X-RateLimit-Remaining` - `X-RateLimit-Reset` - `Retry-After` on `429` If `429` is not manually declared, OpenAPI auto-adds `429 Too Many Requests`.