Errors & limits

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:

HeaderDescription
X-RateLimit-LimitThe cap for the endpoint (e.g. 60).
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetUnix 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" }
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.