openstatus logoDashboard

API Rate Limits

Every request to api.openstatus.dev is counted against a small set of limits. They exist to keep one client from degrading the API for everyone else; a well-behaved integration will never notice them.

Limits

ScopeLimitApplies to
Per API key or token600 requests per minuteall authenticated requests
Per API key or token100 requests per 10 secondsall authenticated requests
Per API key or token60 write requests per minutemutations on /v1, /rpc and /oauth (see below)
Per client IP120 requests per minuteunauthenticated /public/* status endpoints
Per client IP600 failed authentications per minuterequests that present a credential and get a 401, across all credentials

Requests are attributed to the x-openstatus-key header, or to the OAuth bearer token when there is no key. Requests that carry neither are attributed to the client IP.

A write is any POST, PUT, PATCH, or DELETE on /v1 or /oauth, and any RPC on /rpc whose method does not start with Get, List, or Check. CreateMonitor, DeleteStatusReport, TriggerMonitor, and SendTestNotification are writes; ListMonitors and GetStatusPage are not. OAuth registration, token and revocation requests usually carry no credential, so their write budget is per client IP.

The failed-authentication budget only counts 401 responses, so a client sharing an egress IP with others is never charged for their traffic. Once it is exhausted, every request from that IP that carries a credential is rejected until the window resets.

All requests count, including the ones that fail with a 4xx. The health check /ping, the OpenAPI documents, and the OAuth discovery endpoints under /.well-known/ are exempt.

Note

Limits are enforced per API server instance, so the numbers above are approximate. A client whose requests are spread across several servers gets slightly more headroom, never less.

Rate limited response

A request over a limit is rejected before it reaches the route. The response is 429 Too Many Requests with a Retry-After header in seconds and the standard error envelope:

HTTP/1.1 429 Too Many Requests
Retry-After: 7
Content-Type: application/json

{
  "code": "TOO_MANY_REQUESTS",
  "message": "Rate limit exceeded, retry later",
  "docs": "https://www.openstatus.dev/docs/api-references/errors/code/TOO_MANY_REQUESTS",
  "requestId": "1c4b8f1e-3f4b-4f79-9f9d-2a6d7f0b6c1a"
}

On /rpc the body is a Connect error instead, so ConnectRPC clients such as the Node.js SDK raise a ConnectError with code resource_exhausted:

{ "code": "resource_exhausted", "message": "Rate limit exceeded, retry later" }

When a server is overloaded it may also answer 503 Service Unavailable with a Retry-After header: code: "SERVICE_UNAVAILABLE" in the envelope, unavailable on /rpc. Treat it exactly like a 429.

Retrying

  • Honour Retry-After. Wait at least that many seconds before sending the next request. The header is always present on 429 and 503.
  • Back off exponentially on repeated rejections and add jitter so several workers do not retry in lockstep.
  • Do not retry in a tight loop. A client that retries immediately, without a timeout, stays rate limited for the whole window and starves its own successful requests.
  • Batch reads. Use the List* RPCs and /v1 list endpoints instead of fetching resources one by one.
  • Cache status. Public status endpoints change rarely; poll them at most every few seconds, not on every page view.
async function withRetryAfter(fn: () => Promise<Response>): Promise<Response> {
  for (let attempt = 0; ; attempt++) {
    const res = await fn();
    if ((res.status !== 429 && res.status !== 503) || attempt === 5) return res;
    const seconds = Number(res.headers.get("retry-after") ?? 2 ** attempt);
    await new Promise((r) => setTimeout(r, seconds * 1000 + Math.random() * 250));
  }
}

Need more?

The limits are sized well above what any workspace uses today. If your integration legitimately needs more, contact ping@openstatus.dev with your workspace slug and the request pattern.