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.
Global rate-limit fallback
Section titled “Global rate-limit fallback”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:
dev_modeintegrations bypass entirely. Adev_modeintegration is never rate-limited — the check returns before any tier is consulted.- Integration limit — an integration’s own configured limit, if it has one.
- Project tier — the project’s plan/tier limit, if set.
- 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.
Caching
Section titled “Caching”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.
Audit retention window
Section titled “Audit retention 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.