Files
Checkmate/docs/api-content/rate-limiting.md
T
gorkem-bwl 9aa2594df7 docs(api): add public API documentation site (Astro Starlight)
## Changes
- Add narrative API documentation in docs/api-content/ (introduction,
  quickstart, authentication, conventions, errors, rate limiting)
- Scaffold Astro Starlight site at docs/api/ that consumes prose from
  the sibling api-content folder and auto-generates the endpoint
  reference from server/openapi.json via starlight-openapi
- Wire Checkmate brand color (#13715B) into Starlight via custom CSS
- Add vercel.json so the docs site deploys cleanly as an Astro project
  with root directory docs/api/

## Benefits
- Public API docs ready to publish at checkmate.so/docs
- Endpoint reference stays in sync with the OpenAPI spec automatically
- Zero third-party SaaS dependency: site is fully self-hosted on Vercel
- Contributors edit plain markdown; no Astro knowledge needed for prose
2026-05-22 10:21:10 -04:00

2.8 KiB

title, description
title description
Rate limiting Per-IP request limits, headers, and how to back off.

Rate limiting

Checkmate applies two per-IP rate limiters, both with a 60-second window:

Limiter Applies to Limit
General All requests under /api/v1/* 600 requests / 60 seconds
Auth-sensitive /auth/login, /auth/register, /auth/recovery/*, /status-page/:url/unlock 15 requests / 60 seconds

The two limiters are independent. Auth-sensitive endpoints count against both buckets — you can hammer /auth/login 15 times in a minute before being rate-limited there, but a single client can never exceed 600 total requests per minute regardless of which endpoints are called.

Headers

Every response includes the standard RateLimit-* headers so you can pace yourself proactively:

RateLimit-Limit: 600
RateLimit-Remaining: 587
RateLimit-Reset: 42
Header Meaning
RateLimit-Limit The cap for the current window.
RateLimit-Remaining Requests left before you hit 429.
RateLimit-Reset Seconds until the window resets.

When you exceed the limit

You receive HTTP 429 Too Many Requests:

{
  "success": false,
  "msg": "Too many requests"
}

The response also includes Retry-After:

Retry-After: 42

Wait that many seconds before retrying. If Retry-After is missing, fall back to the RateLimit-Reset header.

For one-off scripts, simple sleep-then-retry is fine:

async function callWithRetry(url, opts, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch(url, opts);
    if (res.status !== 429) return res;
    const retry = Number(res.headers.get("Retry-After") ?? 60);
    await new Promise(r => setTimeout(r, retry * 1000));
  }
  throw new Error("Rate limit retry exhausted");
}

For higher-throughput integrations, watch RateLimit-Remaining and pace your sender to stay below the limit rather than waiting for 429.

Limits and proxies

Rate-limiting is per client IP, derived from the connection — or, when Checkmate is behind a reverse proxy with trust proxy configured, from the leftmost public IP in X-Forwarded-For.

If many of your clients share an egress IP (NAT, corporate proxy), they share the bucket. In that case, an operator should either:

  • Increase the limits (server code change required), or
  • Make sure each client connects from a distinct IP.

Limits are not contractual

The numbers above reflect the current defaults. An operator can change them at any time (they're hardcoded in server/src/middleware/rateLimiter.ts). If you're building an integration that depends on a higher limit, coordinate with the operator first.

Next: API reference — auto-generated from the OpenAPI spec.