Skip to content

Admin settings

Most of the wrapper’s behaviour is fixed at deploy time by environment variables. A small, deliberate set of values is instead runtime-configurable by the super admin, so an operator can react to abuse or a policy change without shipping a new build. These live in the system_settings table (a plain key → jsonb store) and are exposed under /admin/settings/*.

Both endpoints below are admin-session-gated: 401 with no session, 403 when the session role is not admin.

Terminal window
GET /admin/settings/rate-limit # → { max: 1000, window_ms: 60000 }
PUT /admin/settings/rate-limit # { "max": 500, "window_ms": 60000 }

The body is { max, window_ms } — max an integer ≥ 1 (requests per window), window_ms an integer ≥ 1000 (the window length in milliseconds). A PUT upserts both keys (rate_limit_max / rate_limit_window_ms) and returns the new pair; an out-of-range value is 422.

Where this sits — the rate-limit hierarchy

Section titled “Where this sits — the rate-limit hierarchy”

Rate limiting is layered, and this global fallback is only one rung. On every /project/* request the limit is resolved most-specific-first:

  1. dev_mode integrations bypass entirely. A dev_mode integration is never rate-limited — the check returns before any tier is consulted.
  2. Integration limit — an integration’s own configured limit, if it has one.
  3. Project tier — the project’s plan/tier limit, if set.
  4. Global fallback (this setting) — the admin-configured value in system_settings, or, when it is unset or invalid, the env default (RATE_LIMIT_MAX / RATE_LIMIT_WINDOW_MS, defaulting to 1000 requests / 60000 ms). The env value is the floor beneath this rung, not a separate tier: the global tier is “the override if present, otherwise env.”

The point of making the global rung runtime-editable is that it is the one an operator most often needs to change right now — dialing the platform-wide default down under load, or up for a launch — without touching per-integration or per-project configuration and without a redeploy.

Reads are cached for 60 seconds, matching the integration-policy’s project-tier cache, so the hot request path does not hit the database on every call. A PUT invalidates the cache immediately. The read path also never throws: if the database is unreachable it falls back to the env defaults rather than failing the request.

Every /project/* response still carries X-RateLimit-Limit / -Remaining / -Reset, and a caller over the effective limit gets 429 + Retry-After — regardless of which rung produced that limit.

Terminal window
GET /admin/settings/audit-retention # → { retention_days: 365 }
PUT /admin/settings/audit-retention # { "retention_days": 730 }

The body is { retention_days }, an integer in 1..3650 (up to ten years). The default when unset is 365 days. The value is stored under audit_retention_days, and a daily purge job deletes audit_logs rows older than the window. Purge is the only deletion an audit row can experience — the log is otherwise append-only.

See Audit log & non-repudiation for what is recorded, the actor model, and how to query the trail.