Skip to content

Full Configuration Reference

Every field validated by Pydantic at startup, with its default. See Configuration for a faster path to a working setup, or Environment Variables for the override list.


storage:
backend: "mongodb" # mongodb | memory
mongodb:
uri: "mongodb://user:pass@host:27017/db?authSource=db"
database: "mcp_analyzer"
# collection names below all have defaults — override only if you need to
cors:
allow_origins: ["https://your-app.example.com"] # ["*"] for dev
allow_credentials: true
allow_methods: ["GET", "POST", "PUT", "PATCH", "DELETE"]
allow_headers: ["*"]
auth:
api_key: "change-me-to-a-strong-secret" # null → disabled (anonymous default-org owner)
jwt:
# secret: "set-me-in-production" # random per-process if omitted
refresh_ttl_days: 14 # reserved; refresh-token rotation not yet implemented
# session.access_ttl_minutes isn't set here — it's platform-managed, see below
two_factor:
# secret_encryption_key: "set-me-in-production" # random per-process if omitted
rate_limit:
enabled: true # the thresholds are NOT set here — they're platform-managed, see below
llm:
# enabled is NOT set here — it's platform-managed, see below
# credential_encryption_key: "set-me-in-production" # random per-process if omitted
deployment:
mode: "self_hosted" # self_hosted | cloud — see below

That’s the entire file — logging, registration, server, session, mcp, telemetry, retention, jobs, analysis, org_offboarding, and github are deliberately absent, and rate_limit appears with only its on/off switch; see below.

The example configs shipped in each repo (OpenTremor-core/configs/opentremor-core*.yaml, OpenTremor-platform/configs/opentremor-platform*.yaml) point here rather than restating any of it — this page is the single source of truth for which sections a config file can set.


Platform settings — admin-editable subset

Section titled “Platform settings — admin-editable subset”

logging, registration, server, session, mcp, telemetry, retention, jobs, analysis, org_offboarding, and github are not read from this file or from any environment variable. Their only possible baseline is a fixed, hardcoded default (the Pydantic field default shown in each section below) — create_app() resets these sections to that default the moment the config is loaded, discarding whatever the file/env contained for them, before anything else in the process reads them. The only way to change one is GET/PATCH /admin/settings (superadmin only — the dashboard’s Platform Admin → Settings page).

This is deliberate, not a limitation: previously, an admin-editable field with no override yet simply reflected whatever the local YAML happened to contain — which meant two instances that had never been touched via the admin UI could show different “effective” values purely because they shipped with different config files, with no way to tell a deliberate choice from incidental yaml drift. Resetting these sections to one fixed, documented default removes that ambiguity by construction: there is exactly one baseline, and exactly one way to change it.

Covered — hardcoded default + live database override, no restart, ever: logging, registration, server, session, rate_limit (thresholds only), retention, jobs, analysis, org_offboarding, github. A PATCH takes effect for the very next request (for session.access_ttl_minutes, the very next login/session-switch — it doesn’t retroactively extend a cookie a user is already holding; for retention.telemetry_days/retention.audit_days, immediately — pushed live onto the running MongoDB TTL indexes via collMod, not just re-read on the next request). See Data Retention for retention’s own field-by-field detail, Platform Admin — Running a stuck-job sweep manually for jobs’s, and Deleting an organization for org_offboarding’s — all three are covered here only at the same summary depth as every other section.

Excluded — genuinely file/env-driven (unaffected by any of the above):

SectionWhy
storageThe MongoDB connection string can’t be stored in the database it points to. A live cutover to a different target is possible, just through a dedicated mechanism instead: POST /admin/storage/switch — see Platform Admin — Switching the storage backend live.
corsSecurity-critical — changing it live from an authenticated session is exactly what shouldn’t be possible.
authapi_key is a bootstrap credential that must work independently of any stateful system, including this API; default_admin_email/default_admin_password only take effect on first boot anyway. Ongoing auth management happens through POST /admin/users/{id}/superadmin, org invites/roles, SSO connections, and service accounts instead — see Authentication.
jwtRotating the session-signing secret invalidates every existing session across every replica. (Session lifetime is a separate, covered section — see session below; only the signing secret itself is excluded here.)
two_factorRotating the secret encryption key makes every already-enrolled user’s TOTP secret unreadable without a re-encryption step.
rate_limit.enabledOne field, not a section — the rest of rate_limit is editable. Whether brute-force protection runs at all is the single change a stolen superadmin session would most want to make, and the one an operator couldn’t undo from inside a compromised deployment.
deploymentIdentifies which deployment this process is (the operator’s cloud instance vs. a customer’s self-hosted one), not a business setting an authenticated admin of either kind should be able to flip — see deployment below.

Excluded — fixed at the hardcoded default, not configurable by any means (not file, not env, not the admin API either):

SectionWhy
mcpRead once, synchronously, inside create_app() itself — the MCP ASGI app is mounted (or not) before the FastAPI app object even exists for a lifespan to attach to, let alone run an async database read. There is no point in the process’s life at which a database override could reach this section before it’s already acted on.
telemetrySame reason as mcpTelemetryMiddleware’s enabled flag is constructed from it before an override could ever apply. (The TTL/retention duration for the spans it produces is a separate, covered section — see retention below and Data Retention; only the on/off switch itself is excluded here.)

See Platform Admin — Settings for the full endpoint reference, Platform Admin — Preparing MongoDB for the POST /admin/storage/prepare-mongodb action that replaces tools/init_mongo.py with a dashboard button, and Platform Admin — Switching the storage backend live for actually cutting the running process over to a prepared target.


KeyDefaultDescription
levelinfoLog verbosity: debug, info, warning, error

Not set in this file — level above is the fixed baseline; change it live via PATCH /admin/settings (logging_level) — see Platform settings.

KeyDefaultDescription
backendmemorymongodb or memory
mongodb.uriFull MongoDB connection URI (required when backend is mongodb)
mongodb.databaseTarget database
KeyDefaultDescription
allow_origins["*"]Allowed origins — restrict to known callers in production
allow_credentialstrueAllow cookies / auth headers
allow_methods["*"]Allowed HTTP methods
allow_headers["*"]Allowed request headers
KeyDefaultDescription
api_keynullBootstrap admin key. null disables authentication entirely
default_admin_emailnullHuman superadmin account, created once at startup if set
default_admin_passwordnullPassword for default_admin_email (min. 8 characters — shorter values are silently skipped, not rejected)

auth.api_key is the bootstrap key — it always resolves to owner of the default org (and platform superadmin — see Platform Admin) and is never stored in the database. Additional keys and human accounts are created at runtime (POST /auth/keys, POST /auth/register) and scoped to whichever org created them — see Authentication.

auth.default_admin_email/default_admin_password create a ready-to-use human login the first time the server starts with them set — idempotent (only acts if no user with that email exists yet, so it’s safe to leave in a config that’s applied on every restart). This is the declarative counterpart to grant_superadmin.py: convenient for a fresh install where running a separate script against a live database isn’t the point.

When auth is enabled, every endpoint except GET /health requires either:

X-API-Key: <key>

or a session cookie / bearer token from POST /auth/session.

KeyDefaultDescription
allow_public_org_creationtrueWhether anyone can create a new organization unilaterally

When set to false, two things 403 for everyone except a superadmin: POST /auth/register with org_name set (and no invite_token), and POST /orgs (an already-logged-in user creating an additional org). Two things are unaffected either way, since neither creates a new organization: registering with neither org_name nor invite_token (an org-less account — see Invitations), and registering with invite_token (joining an existing org an invite grants access to). Use this to force invite-only growth on a publicly-reachable instance without touching who can register an account at all.

Not set in this file — change it live via PATCH /admin/settings (registration_allow_public_org_creation), no restart — see Platform settings above.

Not set in this file at all — every field below is a fixed baseline, changed only via PATCH /admin/settings (server_* fields), no restart. On a fresh deployment that needs dashboard_base_url/public_base_url set from day one (see the reverse-proxy note below), that means one PATCH /admin/settings call right after first boot instead of a config-file line.

KeyDefaultDescription
max_input_size_mb10Maximum ingest/analyze body size (1–500 MB)
sync_analysis_max_units5POST /{analyzer}/{namespace}/analyze runs inline (sync, 200) at or below this many ingested units; above it, a background job is created instead (202)
public_base_urlnullPublic URL used for external links in reports (e.g. the HTML report link in markdown_light), and to build the GitHub App manifest flow’s webhook/redirect URLs (see GitHub App Integration). When null, report links fall back to X-Forwarded-Proto/X-Forwarded-Host headers, then request.base_url — but the manifest flow has no such fallback (there’s no in-flight request to derive it from at redirect time) and returns 501 until this is set.
dashboard_base_urlnullWhere the dashboard is reachable — used only to build the redirect target after the GitHub App manifest flow’s callback. Falls back to public_base_url when unset, which is correct for the default same-origin, path-prefixed deployment; only set this explicitly if the dashboard is on a separate Service/ingress path.

Controls the built-in metrics and tracing system.

KeyDefaultDescription
enabledtrueCollect and expose telemetry spans

When enabled, every HTTP request is recorded as a span, and enriched domain spans are recorded on ingest and analysis submission. Spans are queryable via GET /metrics/* (org-admin only, org-scoped).

TTL/purge policy for telemetry spans, the audit log, findings, and raw analyzed resource content. Covered by PATCH /admin/settings — thin summary table only; see Data Retention for the full policy this backs (why each field has the lifecycle it does, how TTL-based vs. sweep-based fields differ in when they take effect, and the severity/status tiering for findings).

KeyDefaultBounds
telemetry_days301–365
audit_days73030–3650
finding_low_medium_days1801–3650
finding_high_critical_days7301–3650
resource_content_days901–3650
sweep_enabledtrue
sweep_interval_hours241–168

telemetry_days/audit_days are pushed live onto the running MongoDB backend’s TTL indexes via collMod the moment they’re PATCHed — no restart, no re-prepare. The other fields govern the retention sweep (POST /admin/retention/run), normally triggered by OpenTremor-task-scheduler’s heartbeat rather than this endpoint’s value alone. Ignored by the in-memory backend the same way every other Mongo-specific setting is.

Timeout/cadence policy for the stuck-job sweep. Platform-owned: job tracking is generic infrastructure, not analysis-specific. There is no durable job queue (an async .../analyze job is a plain in-process background task), so a process crash or restart mid-run otherwise leaves a job frozen at queued/running forever. Covered by PATCH /admin/settings; see Platform Admin — Running a stuck-job sweep manually for the full endpoint reference.

KeyDefaultBounds
stuck_timeout_minutes605–1440
sweep_enabledtrue
sweep_interval_hours11–168

stuck_timeout_minutes is how long a job can go without a progress update (updated_at, stamped on every status transition and progress report) before the sweep (POST /admin/jobs/sweep) presumes it orphaned and marks it failed — resumable afterward via POST /jobs/{job_id}/retry. sweep_enabled/sweep_interval_hours govern the sweep itself, normally triggered by OpenTremor-task-scheduler’s heartbeat rather than this endpoint’s value alone, same shape as retention above.

How the server-side analysis loop drives the LLM — the only section owned outright by the analysis engine rather than the platform. (llm, below, is about access: whether the feature is on and what key encrypts per-org credentials. That’s generic; this isn’t.) Not set in the config file — every field is a fixed baseline changed via PATCH /admin/settings (analysis_* fields), no restart, which matters because these are exactly the knobs you reach for while a provider is misbehaving. See Architecture — the drain loop for what each one bounds.

KeyDefaultBoundsDescription
max_concurrent_units41–32Units analyzed in parallel within one run
llm_timeout_seconds120.05–600Per-call deadline handed to the provider SDK
llm_max_attempts31–6Total attempts per unit, not retries on top of one — 1 disables retrying
llm_retry_backoff_seconds1.00.1–30Base delay for exponential backoff with full jitter

max_concurrent_units is a per-run bound: N concurrent analyze jobs can have N × this many calls in flight. That’s deliberate — the backstop against runaway spend is the organization’s monthly LLM budget, which the loop honours per unit. Raising it past what your provider’s rate limit allows makes 429s (retried, but not free in wall clock) more likely before it makes anything faster.

llm_timeout_seconds replaces the provider SDK’s own default, which is far longer than a job’s useful deadline — Anthropic’s is 10 minutes. A timed-out call is treated as transient and retried, not fatal.

Only transient failures are retried: 408/409/429 and 5xx, plus timeouts and dropped connections. An invalid API key, a rejected schema or an unparseable response fails the run on the first attempt, because asking again reproduces it exactly.

The grace period between DELETE /admin/organizations/{org_id} scheduling an organization’s deletion and it becoming permanent. Covered by PATCH /admin/settings; see Platform Admin — Deleting an organization for the full endpoint reference and exactly what does and doesn’t survive a purge.

KeyDefaultBounds
grace_period_days301–365

Read fresh per request, same as every other covered section — a change applies to any still-pending organization’s computed purge_after immediately, and to POST /admin/organizations/purge-pending’s next run.

Controls the FastMCP endpoint mounted alongside the REST API.

KeyDefaultDescription
enabledtrueMount the MCP server at mount_path
base_urlhttp://localhost:8000URL FastMCP uses for internal HTTP calls back to FastAPI
mount_path/mcpPath prefix for MCP endpoints (/mcp/sse, POST /mcp/)

base_url is always http://localhost:8000 in every environment (bare-metal, Docker, Kubernetes) — FastMCP calls back to the same process via loopback, no service-level routing needed, so this never needs to be anything else.

Signs human-user session tokens (POST /auth/session, POST /auth/register).

KeyDefaultDescription
secretrandom per-processHS256 signing secret. Must be set explicitly (and shared across every replica) in any real deployment via JWT_SECRET — otherwise sessions won’t validate across pods, and every restart invalidates all logged-in sessions
refresh_ttl_days14Reserved — refresh-token rotation isn’t implemented yet

Session token lifetime — kept separate from jwt so it can be platform-managed (live-editable via PATCH /admin/settings) without also exposing jwt.secret, whose rotation invalidates every existing session across every replica.

Not set in this file or via env vars — change it live via PATCH /admin/settings (session_access_ttl_minutes), no restart — see Platform settings above. Takes effect for newly issued sessions only (login, register, session-switch); doesn’t retroactively extend a session a user is already holding.

KeyDefaultDescription
access_ttl_minutes480Session token lifetime (1–1440)

A platform section despite the name — see Platform architecture — LLM. It carries the base only: whether the deployment offers LLM-backed features, and the key their secrets are encrypted under. The provider clients belong to the product.

KeyDefaultDescription
enabledtrueAdmin-editable — Platform Admin → Settings → General → LLM Service, or PATCH /admin/settings {"llm_enabled": false}. Whether this deployment offers LLM-backed features at all. Like every admin-editable field, a value in the config file is discarded at boot; the API is the only way to change it
credential_encryption_keyrandom per-processFile/env only, and never returned by the API. Encrypts secrets at rest (Fernet): SSO connection client secrets, per-org LLM provider API keys, and the GitHub App private key. Same must-be-set-in-production rule as jwt.secret via LLM_CREDENTIAL_KEY — otherwise stored secrets become unreadable across restarts/replicas

llm is the one section split across the two regimes: enabled is admin-editable, the key is not. That is a per-field distinction, not a per-section one — the key is registered nowhere, so it is absent from the PATCH body model (sending llm_credential_encryption_key is a 422), absent from effective in GET /admin/settings, and left at its file/env value rather than being reset to a fresh per-process default at boot.

The key encrypts storage, not any particular provider — it’s shared across whichever of anthropic/openai/mistral an org stores a credential for. See Server-Side Analysis.

With enabled: false, these return 501: POST /{analyzer}/{namespace}/analyze, POST /{namespace}/analyze/auto, and POST /orgs/{org_id}/llm-credentials. Listing and deleting stored credentials deliberately keep working, so an operator who turns the feature off can still see and clean up what organizations left behind. Ingest, analyzer listing and the client-led analysis workflow are unaffected — none of them call a provider.

KeyDefaultDescription
secret_encryption_keyrandom per-processEncrypts enrolled users’ TOTP secrets at rest (Fernet). Same must-be-set-in-production rule as jwt.secret and llm.credential_encryption_key via TWO_FACTOR_SECRET_KEY — otherwise every already-enrolled secret becomes unreadable across restarts/replicas

File/env only, like the other two encryption keys — see the exclusion table in Platform settings above. Note this is only the encryption key: whether an org requires 2FA is per-org data (PATCH /admin/organizations/{org_id}/2fa-policy), not config. See Two-Factor Authentication.

Attempt budgets for the endpoints that verify a credential.

This section is split: the five thresholds are admin-editable, enabled is file/env only. Change the thresholds at Platform Admin → Settings → Security (or PATCH /admin/settings) — they apply to the very next request, no restart, and anything a config file says about them is discarded at boot like every other admin-editable section. enabled is the opposite: it is read from the file, is never returned by GET /admin/settings, and PATCH rejects it with a 422.

The asymmetry is the point. Tightening or loosening a threshold is an ordinary operational decision, and the bounds below cap how loose “loose” can get. Turning the protection off altogether is the single change a stolen superadmin session would most want to make, and the one an operator couldn’t undo from inside a compromised deployment — so it requires reaching the deployment’s config, alongside the ingress rules that are the other half of the same protection. (The llm section is split the same way and in the opposite direction, for the mirror-image reason — there the toggle is the safe live decision and the secret is the dangerous one.)

KeyDefaultEditableDescription
enabledtruefile/env onlyWhether the application layer runs at all. Only sensible to turn off when something in front already does this (an API gateway, WAF, Cloudflare) — off means unlimited failed attempts
login_max_attempts10Failed logins per account (1–1000) before POST /auth/session stops checking the password at all. Cleared by any successful login
login_window_minutes15The window that budget is measured over (1–1440)
mfa_max_attempts5Second-factor attempts per account (1–100), over a fixed 15-minute window. Counts correct codes too — a challenge ends either way. Cleared by a successful second factor
ip_max_attempts60Failed credential presentations per source IP (1–10000), across every credential type at once: login, API key, SCIM bearer token, invite and public-report link tokens
ip_window_minutes15The window for the per-IP budget (1–1440)

Exceeding a budget answers 429 with a Retry-After header. The response to the attempt that crosses a threshold is unchanged (a 401 stays a 401), so nothing in the API reveals where the line sits — only the next attempt is turned away.

Two budgets, keyed differently, on purpose. The per-account ones are the half a distributed password-spray cannot dodge by rotating source addresses; the per-IP one is the catch-all for the guessing surfaces that have no account to key a budget to. A success clears an account’s budget but never the IP’s — that one is keyed by something many unrelated users share (a NAT, an office egress), so letting a success reset it would hand an attacker a free reset by authenticating to their own account.

Configures the platform’s built-in GitHub App integration — the fallback identity used by orgs that install the shared App rather than registering their own via the manifest flow (POST /orgs/{org_id}/integrations/github/manifest). Orgs using the platform App “install” it and register their own installation_id separately (POST /orgs/{org_id}/integrations/github); orgs using their own App need none of this section set at all.

Not set in this file or via GITHUB_* env vars — change it live via PATCH /admin/settings (github_app_id/github_private_key/github_webhook_secret), no restart — see Platform settings above.

KeyDefaultDescription
app_idnullGitHub App ID
private_keynullApp’s PEM private key (RS256), used to sign App-level JWTs
webhook_secretnullHMAC-SHA256 secret configured on the App’s webhook — verifies X-Hub-Signature-256
KeyDefaultDescription
modeself_hostedself_hosted or cloud

File/env only (DEPLOYMENT_MODE) — never admin-API-editable, even though nothing structurally prevents it the way mcp/telemetry are prevented; it identifies which deployment this process is, not a business setting an authenticated admin should ever flip live.

  • self_hosted (the default — every OSS/quickstart/Helm config leaves this unset) — every org is unconditionally entitled to run every analyzer, regardless of plan_tier/demo_analyses_used.
  • cloud (set only on the operator’s own SaaS config) — an org needs plan_tier == "paid", or is still within its one-time, lifetime 100-analysis free demo. See Billing / quota enforcement for the full mechanism (controllers/entitlements.py).