Rate limits
Per-IP, per-endpoint throttling rules and how to recover from 429 responses.
Obzerv enforces 60 requests/minute per IP per endpoint for the public API. Auth endpoints (login, signup, password reset) are tighter: 5 requests/minute per IP. The limiter is an in-memory sliding window — counts reset roughly one minute after your first request in the window.
Rate limits are not plan-tiered. The same caps apply to all accounts. If you have a legitimate need for higher throughput (large data migration, reseeding monitors), email support@obzerv.xyz with traffic patterns and we will look at allowlisting your egress IP.
What you get back
Every API response — both successful calls and 429s — carries the standard rate-limit metadata in headers, so you can budget without a second lookup:
| Header | Description |
|---|---|
X-RateLimit-Limit | The cap for the endpoint (e.g. 60). |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Reset | Unix timestamp (seconds) when the window resets. |
Retry-After | (only on 429) seconds to wait before retrying. |
On exceed:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1714052400
Retry-After: 38
{ "error": "Rate limit exceeded" }Recommended client behavior
async function call(url: string, opts: RequestInit, attempt = 0): Promise<Response> {
const res = await fetch(url, opts);
if (res.status !== 429 || attempt >= 5) return res;
const wait = Number(res.headers.get('retry-after') ?? 1) * 1000;
await new Promise((r) => setTimeout(r, wait + 250 * 2 ** attempt));
return call(url, opts, attempt + 1);
}After five attempts, surface the 429 to the caller — sustained throttling means something is off (looped script, misconfigured cron) that retrying won't fix.
What counts as one request
Each method+path is a separate bucket. GET /monitors and GET /monitors/{id} don't share a counter. Sequential reads spread across endpoints rarely throttle — bursts of writes to the same endpoint are the typical cause.
Behind a proxy
Client IP is read from X-Real-IP (set by our edge Caddy). When that's missing, the last entry of X-Forwarded-For is used as a fallback (Caddy overwrites it at the edge, so spoofed XFF values from clients are dropped).
If you call from a corporate NAT and end up sharing an IP with hundreds of colleagues, you may hit the limit faster than expected. Email us — we can accommodate.