Appearance
Environment variables
Every process (API, worker, migrate) validates these at start-up with EnvSchema and refuses to start on an invalid value. Required means no default and not optional; an optional variable with no default is unset when absent. Regenerate with pnpm env:reference.
Database
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
DATABASE_URL | url | yes | ||
DATABASE_APP_URL | url | no | Non-owner connection used by the API and worker so row-level security applies. |
Temporal
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
TEMPORAL_ADDRESS | string | localhost:7233 | no | |
TEMPORAL_NAMESPACE | string | default | no | |
TEMPORAL_TASK_QUEUE | string | aletheia-workflows | no | |
TEMPORAL_BACKTEST_TASK_QUEUE | string | aletheia-backtests | no | Long-running backtests run on their own queue so they never starve decisions. |
TEMPORAL_APP_TASK_QUEUE | string | aletheia-apps | no | App calls (through the app runner) and document scans run on their own queue so vendor latency never starves decisions. |
Apps
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
APP_CATALOG_DIR | string | no | Where the API finds the catalogue's index.json and modules (catalog/dist in development, /app/catalog in the image); unset, no platform apps are published at start-up. | |
APP_RUNNER_URL | url | no | Where the API and the worker reach the app runner (POST /v1/invoke and the other calls); unset, apps are off: nothing is installed or run, and the catalogue is not published. | |
APP_RUNNER_TOKEN | string (min 32 chars) | no | Bearer the API, the worker and the runner share: every call to the runner carries it, and the runner presents it to the API's internal routes. Unset, the runner accepts every request (development only; in production it refuses to start without one). | |
APP_CALL_TOKEN_SECRET | string (min 32 chars) | no | Signs the tokens a call reads the tenant's documents with (GET /internal/app-calls/…); held by the API and the worker only, never by the runner. 32 characters at least. | |
APP_REVIEW_DEFAULT | off | required | off | no | Whether a new tenant's own app versions wait for the operator's review before they publish: required or off. Applied when the tenant is created, by signup and pnpm tenant:add; the operator changes one tenant's with pnpm ops. |
APP_SIGNATURES | off | optional | required | off | no | Whether tenants' app versions carry Sigstore provenance: off takes none; optional verifies a bundle a tenant attaches and shows who signed; required publishes, approves, installs and upgrades to a tenant's version only when a signer of APP_TRUSTED_SIGNERS signed it. Platform versions are never gated. |
APP_TRUSTED_SIGNERS | string | [] | no | The signers the operator trusts, as a JSON array: keyless identities { "name", "issuer", "identity" }, the certificate's OIDC issuer exactly and a glob over its subject alternative name (* within a path segment, ** across them), and pinned ECDSA P-256 keys { "name", "publicKey" } in PEM. Trust is computed against it wherever provenance is shown or gates a publish, so removing a signer revokes it at the next start. |
APP_SIGSTORE_TRUSTED_ROOTS | string | public-good | no | What keyless signatures are verified against, comma-separated: public-good, Sigstore's public instance, its trusted root fetched and refreshed over TUF from tuf-repo-cdn.sigstore.dev; and absolute paths of trusted_root.json files, such as GitHub's instance (gh attestation trusted-root), a private Sigstore or an offline copy. Bundles signed with a pinned key need none. |
APP_SIGSTORE_TUF_CACHE | string | no | Where the API caches Sigstore's TUF metadata; defaults to aletheia-sigstore under the temp directory, which stays writable where the root filesystem is read-only. It holds the root the metadata is checked against, so it must be a directory of the API's user that no other user may write: a missing one is made so, and a symbolic link, another user's directory or one others may write is refused. | |
APP_CIRCUIT_FAILURE_THRESHOLD | integer ≥ 1 | 5 | no | The vendor's outages in a row (errors the app calls retryable, and timeouts) that open an install's circuit, 1 to 100, for installs that set none; read by the worker, and shown by the API on each install. |
APP_CIRCUIT_OPEN_SECONDS | integer ≥ 5 | 30 | no | Seconds an open circuit refuses calls before one trial call goes out, 5 to 3600, for installs that set none. Set both on the API and the worker. |
APP_CACHE_ENTRIES | integer ≥ 1 | 16 | no | The cached values one install keeps (ctx.cache), 1 to 64: a call's write past it removes the values written longest ago. Read by the API and the worker, which store what calls cache. |
APP_CACHE_TOTAL_BYTES | integer ≥ 8192 | 65536 | no | The bytes of one install's cached values together, as UTF-8, 8192 to 524288: a call's write past it removes the values written longest ago. Set it beside APP_CACHE_ENTRIES. |
APP_RUNNER_API_URL | url | http://localhost:4000 | no | Where the runner fetches modules and documents from: the API's internal routes. |
APP_RUNNER_HOST | string | 0.0.0.0 | no | |
APP_RUNNER_PORT | integer > 0 | 4100 | no | |
APP_RUNNER_CONCURRENCY | integer ≥ 1 | 8 | no | Calls one runner process runs at once; past it the runner answers 429 busy. |
APP_RUNNER_TENANT_CONCURRENCY | integer ≥ 1 | 4 | no | Calls one tenant may have in flight on one runner process at once. |
APP_RUNNER_CACHE_DIR | string | no | Where the runner keeps fetched modules on disk; defaults to aletheia-app-runner under the temp directory. | |
APP_RUNNER_CACHE_MODULES | integer ≥ 1 | 32 | no | Compiled modules the runner keeps in memory; the least recently used go first. |
APP_RUNNER_ALLOW_PRIVATE_NETWORKS | true | false | false | no | Lets apps reach http:// URLs, any port of an approved hostname and private, loopback and link-local addresses. For development stacks only: in production vendors are https and public. The runner applies it to every request; the API, to the hosts an install's config adds (installHosts: http, localhost and IP addresses pass). Set the same value for both. |
APP_RUNNER_WARM_PLUGINS | integer ≥ 0 | 4 | no | Instances one runner process prepares ahead of calls, 0 to 64, for the modules it has run: each serves exactly one call and is discarded. 0 prepares none: every call starts its own. |
APP_RUNNER_WARM_MEMORY_MIB | integer ≥ 0 | 256 | no | The memory the prepared instances' modules may grow to together, in MiB (each at the memory maximum its module's bytes declare, which the engine enforces): an instance that would pass it is not prepared. |
APP_RUNNER_WARM_IDLE_MS | integer ≥ 1000 | 300000 | no | Milliseconds a module goes uncalled before its prepared instances are discarded. |
APP_RUNNER_KEEPALIVE_MS | integer ≥ 0 | 4000 | no | Milliseconds an idle connection to a vendor stays open for the tenant's next request, 0 to 60000: connections are kept per tenant, never shared with another, and each new one resolves and checks the vendor's name. 0 opens a connection for every request. |
API
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
API_HOST | string | 0.0.0.0 | no | |
API_PORT | integer > 0 | 4000 | no | |
API_INTERNAL_PORT | integer ≥ 1 | 4001 | no | The API's second listener, for the app runner only (GET /internal/app-modules/…, GET /internal/app-calls/…): never behind the ingress or a public Service port. |
API_CORS_ORIGINS | string | "" | no | Browser origins allowed to call the API directly; comma-separated. None by default: the admin console and the collection terminal call it through their own origin. |
API_RATE_LIMIT_PER_MINUTE | integer ≥ 0 | 600 | no | Requests a minute one caller may make before the API answers 429 rate_limited with Retry-After; per API instance. A caller is a tenant's person or service user, an applicant's submission, or on the public routes (vendor webhooks, flow previews) a client address; health checks are not counted. 0 turns the limit off. |
Zitadel (identity)
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
ZITADEL_ISSUER | url | no | Identity provider (Zitadel). The API refuses to start without the required subset. | |
ZITADEL_PROJECT_ID | string | no | ||
ZITADEL_API_CLIENT_ID | string | no | ||
ZITADEL_API_CLIENT_SECRET | string | no | ||
ZITADEL_DIRECTORY_PAT | string | no | PAT of the read-only machine user the reviewer directory uses; unset disables the directory. | |
ZITADEL_MANAGEMENT_PAT | string | no | PAT of the machine user that manages service users and their keys (Connect › API keys) and the tenants' people (Settings › Team); it needs user and user-grant management in the tenant organisations. Unset disables those routes. | |
ZITADEL_SIGNUP_PAT | string | no | PAT of the aletheia-signup machine user (instance role IAM_ORG_MANAGER): POST /signup creates a workspace's organisation, grants it the project and creates its admin. Signup is off without it, RESEND_API_KEY, SIGNUP_EMAIL_FROM and SIGNUP_CONSOLE_URL. |
Signup
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
RESEND_API_KEY | string | no | Resend API key; the signup email carries the new admin's temporary password. | |
SIGNUP_EMAIL_FROM | string (min 3 chars) | no | Sender of the signup email, Name <address> on a domain verified in Resend. | |
SIGNUP_EMAIL_REPLY_TO | string | no | Reply-To of the signup email; the sender when unset. | |
SIGNUP_CONSOLE_URL | url | no | The admin console's public address, in the signup email and the answer to the form. | |
TURNSTILE_SECRET_KEY | string | no | Cloudflare Turnstile secret. When set, POST /signup requires a token that passes; unset accepts the form without a bot check (development only). | |
SIGNUP_MAX_PER_HOUR | integer ≥ 0 | 20 | no | Workspaces signup may create per hour across the deployment; 0 turns signup off. |
Submission tokens
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
SUBMISSION_TOKEN_SECRET | string (min 32 chars) | no | Signs the links end customers use to open a collection flow. |
Collection flow
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
COLLECTION_FLOW_URL | url | no | Public base URL of the collection terminal, used to build those links. | |
COLLECTION_TERMINAL_TENANT_NAME | string | no | The name the collection terminal shows for tenants without their own branding (Settings › Branding). A tenant's own name or logo replaces it and the logo below together. | |
COLLECTION_TERMINAL_LOGO_URL | url | no | The logo for those tenants (https). | |
COLLECTION_TERMINAL_PRIMARY_COLOR | string | no | The brand color for those tenants, #rgb or #rrggbb. | |
COLLECTION_TERMINAL_COLOR_SCHEME | light | dark | system | no | light, dark or system (the applicant's device) for those tenants. | |
COLLECTION_TERMINAL_SUPPORT_URL | url | no | Where "Help" leads for those tenants (https); a tenant's own support page or address replaces this and the address below together. | |
COLLECTION_TERMINAL_SUPPORT_EMAIL | string | no | The support address for those tenants. | |
COLLECTION_TERMINAL_PRIVACY_URL | url | no | The privacy notice for those tenants (https). | |
COLLECTION_TERMINAL_TERMS_URL | url | no | The terms for those tenants (https). | |
COLLECTION_TERMINAL_RETURN_URL | url | no | Where "Back to …" leads after an approval, for those tenants (https). | |
COLLECTION_TERMINAL_SHOW_DECISION | true | false | false | no | true shows applicants the decision itself (approved, not approved) for flows that set no outcome visibility; false, where their application stands. |
Webhooks
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
WEBHOOK_PUBLIC_URL | url | no | Public base URL vendors call back on: an install's webhooks arrive at <url>/webhooks/app-installs/<install id>. | |
WEBHOOK_ALLOW_PRIVATE_NETWORKS | true | false | false | no | Lets outbound webhooks reach http:// URLs and private, loopback and link-local addresses. For development stacks only: in production endpoints are https and public. |
Object storage
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
STORAGE_ENDPOINT | url | no | S3-compatible object storage for documents (Garage in the dev stack). Unset disables documents. | |
STORAGE_PUBLIC_ENDPOINT | url | no | Endpoint browsers reach for direct uploads and downloads; defaults to STORAGE_ENDPOINT. | |
STORAGE_REGION | string | garage | no | |
STORAGE_BUCKET | string | aletheia-documents | no | |
STORAGE_ACCESS_KEY | string | no | ||
STORAGE_SECRET_KEY | string | no | ||
STORAGE_CORS_ORIGINS | string | no | Comma-separated browser origins allowed to upload directly (the collection terminal and the admin console). |
Documents
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
DOCUMENT_SCANNER | none | none | no | Document scanner engine; none marks every document clean (ClamAV is deferred). |
OpenTelemetry
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT | url | no | OTLP/HTTP collector base URL; unset disables tracing and metric export entirely. | |
OTEL_SERVICE_NAME | string | no | Overrides the default service name (aletheia-api, aletheia-worker). | |
OTEL_TRACES_SAMPLER | string | no | ||
OTEL_TRACES_SAMPLER_ARG | string | no | ||
OTEL_SDK_DISABLED | boolean | false | no | |
OTEL_PG_STATEMENTS | boolean | false | no | Include SQL statement text on database spans (postgres.js). |
Metrics
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
METRICS_PORT | integer > 0 | no | Serves Prometheus /metrics on this port when set (API and worker use their own). |
Worker
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
WORKER_HEALTH_PORT | integer > 0 | no | The worker's GET /healthz port; unset disables the health server. |
Retention
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
RETENTION_APP_CALLBACK_DAYS | integer ≥ 0 | 90 | no | Days the worker keeps the vendor sessions of asynchronous app actions and their webhook events after a session can last settle (the longest wait an action may declare, counted from its start), then deletes them; 0 keeps them forever. |
RETENTION_SUBMISSION_EVENT_DAYS | integer ≥ 0 | 90 | no | Days the worker keeps the drop-off events the collection terminal reports, then deletes them: at least 90, the longest window a funnel reads (GET /stats/flows/:key/funnel), or 0 to keep them forever. |
RETENTION_INTERVAL_MINUTES | integer ≥ 5 | 60 | no | Minutes between the worker's retention sweeps, 5 to 1440. |
Schema check
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
SCHEMA_CHECK | strict | lenient | no | What to do when the database schema is behind the bundled migrations: strict refuses to start (production default), lenient logs a warning (development default). |
General
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
NODE_ENV | development | test | production | development | no | |
LOG_LEVEL | trace | debug | info | warn | error | fatal | info | no | |
SECRET_STORE_KEY | string | no | 32 random bytes, base64: seals the secrets written through the API, app secrets and the signing secrets of outbound webhooks. Unset disables those writes (and outbound webhooks). | |
TRACE_URL_TEMPLATE | string | no | A link to one trace in your tracing backend, with {traceId} where the id goes, such as https://grafana.example/explore?traceId={traceId}. A run's page links the trace of its start there for callers with ops:read; unset or empty, there is no link. | |
DEPLOYMENT_REGION | string | no | Where this deployment keeps its data, as a label such as eu-fra; GET /tenants/me reports it as the tenant's region (Settings › Tenant). Unset or empty reports none. |