Rate Limits

Per-route request limits for Rustume Cloud and connected deployments, including defaults, configuration, and client behavior.

Rustume applies in-memory, per-key rate limits when connected mode is enabled (RUSTUME_CLOUD=true with a configured database). Limits protect service capacity and control the cost of CPU-heavy operations (preview and PDF rendering) without blocking normal editing workflows.

Self-hosted operators can tune limits via environment values, or set RATE_LIMIT_DISABLED=true to skip the middleware entirely (recommended for local cloud development). Browser-local deployments without connected mode do not install these middleware layers.

Scope

DeploymentRate limiting
Browser-local (no RUSTUME_CLOUD)Not applied
Self-hosted connectedApplied; operator-configurable via env vars
Rustume CloudApplied with production defaults

Limits apply both a per-user bucket (authenticated user ID) and a shared per-IP bucket for signed-in routes. When a session resolves to a user ID, middleware checks the IP limit and the user limit; multiple signed-in accounts behind the same office NAT or proxy can therefore receive 429 responses when the shared IP bucket is exhausted, even if each user is below their own quota. Unauthenticated traffic is keyed by client IP only. When TRUSTED_PROXY=true, IP extraction uses X-Real-IP and append-mode X-Forwarded-For from a trusted reverse proxy.

Default limits

All values are requests per minute unless noted.

Authenticated routes

Route groupDefaultBurstExamples
Resume CRUD30030List, get, create, update, delete, GET /api/resumes/export
Resume import10POST /api/resumes/import
Preview render60POST /api/render/preview
PDF render & bulk PDF export20POST /api/render/pdf, GET /api/resumes/export/pdf
Auth10Login, callback, logout, /auth/me
Parse & utility30Templates, parse, validate

Resume CRUD allows short bursts (for example rapid autosave) via a separate burst bucket.

Unauthenticated routes (per IP)

Route groupDefaultExamples
Health60GET /health, GET /version
Metrics60GET /metrics (still requires METRICS_TOKEN)
Other unauthenticated30Any route reached without a session

On cloud deployments (RUSTUME_CLOUD=true), authentication is mandatory, so unauthenticated clients cannot reach render or connected API routes; the unauthenticated bucket mainly covers probes and stray traffic.

Bulk export cap

Bulk export endpoints enforce a separate resume-count cap, independent of per-minute rate limits.

Bulk JSON export (GET /api/resumes/export) counts against the resume CRUD limit group. Bulk PDF export (GET /api/resumes/export/pdf) counts against the PDF limit group and renders each resume sequentially.

EndpointRate limit groupMaximum resumes per request
GET /api/resumes/exportResume CRUD50
GET /api/resumes/export/pdfPDF50

Trigger: before any data is returned, the server fetches up to 51 resumes you own (ordered by updated_at). If a 51st row is present, the entire request fails with 413 Payload Too Large and an error message — there is no silent truncation to the 50 most recently updated resumes.

At or below 50: the response includes every owned resume (most recent first). The single-query LIMIT+1 check keeps the cap decision aligned with the returned payload under concurrent writes.

Above 50: bulk export cannot return the full library in one call. Export resumes individually, delete or archive older entries, or split work across multiple accounts — pagination is not available on these endpoints today.

The cap complements PDF rate limits by bounding per-request CPU and memory even when requests are spaced apart.

Configuration

Override defaults with environment variables (see Environment Variables):

RATE_LIMIT_RESUME_CRUD_PER_MIN=300
RATE_LIMIT_RESUME_CRUD_BURST=30
RATE_LIMIT_IMPORT_PER_MIN=10
RATE_LIMIT_PREVIEW_PER_MIN=60
RATE_LIMIT_PDF_PER_MIN=20
RATE_LIMIT_AUTH_PER_MIN=10
RATE_LIMIT_HEALTH_PER_MIN=60
RATE_LIMIT_METRICS_PER_MIN=60
RATE_LIMIT_UNAUTHENTICATED_PER_MIN=30
RATE_LIMIT_BILLABLE_PER_MIN=30   # templates, parse, validate (not subscription-gated)
TRUSTED_PROXY=true   # only behind a trusted reverse proxy

Invalid values log a warning and fall back to the built-in default.

HTTP 429 response

When a limit is exceeded, the API returns 429 Too Many Requests with:

{
  "error": "Too many requests. Please try again shortly.",
  "retry_after": 12
}

Response headers include Retry-After, X-RateLimit-Remaining: 0, and X-RateLimit-Reset (Unix timestamp). The web app shows a brief toast for save operations and retries with backoff.

Operator notes

  • Limits are process-local (in-memory token buckets). Horizontal scaling multiplies effective capacity per instance; tune env vars if you run multiple replicas.
  • PDF and preview limits exist because Typst rendering is CPU-bound. Normal interactive editing should stay well below defaults.
  • Subscription grace and expiry rules are separate from rate limits; see Subscription Management.