Environment variables
Derived from apps/api/src/configs/index.ts, the Zod schema validated once at boot (validateEnv, called from apps/api/src/main.ts). If validation fails, the process logs service_start_failed and exits — nothing here is silently ignored except the variables explicitly marked optional below.
A handful of variables are read directly via process.env outside this schema — listed separately at the bottom.
Core
| Variable | Default | Required |
|---|---|---|
PORT | 2626 | No |
NODE_ENV | development | No — one of development, production, test |
LOG_LEVEL | info | No — a LogLayer LogLevel value |
SERVER_URL | http://localhost:2626 | No |
APP_URL | http://localhost:3000 | No — dashboard base URL used to build links (e.g. password-reset emails) that must point at the app, not this API |
WORKER_HOST | http://localhost:8000 | No — base URL for the Python worker (apps/worker); see apps/api/src/utils/buildUrl.ts |
MINTER_HOST | unset | Yes in production — browser-minter health endpoint; /ready requires its baked build commit to match the API and worker |
PLATFORM_ADMIN_EMAILS | unset | No — comma-separated emails promoted to platform staff at boot (db/promoteAdmins.ts). Grant-only: removing an email here does not demote it. |
Auth
| Variable | Default | Required |
|---|---|---|
TOOLZERHUB_API_KEY | committed legacy compatibility value | No — a non-empty override shorter than 32 characters fails production boot |
TOOLZERHUB_API_PREVIOUS_KEY | unset | No — one outgoing key accepted only during a staged Actor/client rotation |
JOBS_ID_SECRET | same committed compatibility value | No — keep any override stable so existing opaque job IDs remain decodable |
ACTOR_TELEMETRY_API_KEY | unset (falls back to legacy shared keys) | No — deprecated compatibility for older Actor builds; remove after the fleet uses admin-created keys |
TOOLZERHUB_API_KEY and JOBS_ID_SECRET deliberately share a committed
compatibility default so the published Actor fleet and existing job IDs survive
deployments without secret-store setup. Empty environment values use that
default; non-empty overrides must contain at least 32 characters in production.
TOOLZERHUB_API_PREVIOUS_KEY provides one explicit overlap window and must be
cleared after the fleet moves. Keep any JOBS_ID_SECRET override unchanged. See
auth-and-credits.md for the compatibility boundary and
deployment.md for the release preflight.
Active API keys created by a current platform admin can write Actor telemetry;
ordinary workspace keys cannot. ACTOR_TELEMETRY_API_KEY remains as a
deprecated compatibility credential for older Actor builds: when set, it is
accepted alongside admin-created keys; when unset, the current and previous
legacy shared keys are accepted instead. Remove this compatibility boundary in
a future edition after the published fleet has rotated.
Postgres
| Variable | Default | Required |
|---|---|---|
POSTGRES_HOST | postgres | No |
POSTGRES_PORT | 5432 | No |
POSTGRES_PASSWORD | "" | No (but an empty password is very unlikely to work against a real deployment) |
User and database name are hardcoded in apps/api/src/db/postgres.ts, not configurable via env.
ClickHouse (optional analytics)
| Variable | Default | Required |
|---|---|---|
CLICKHOUSE_URL | http://clickhouse:8123 | No |
CLICKHOUSE_PASSWORD | "" | No — ClickHouse recording is entirely gated on this being non-empty (shouldRecordClickHouseInsight, apps/api/src/observability/apiRequestInsight.ts). Unset, main.ts logs dependency_disabled and skips ClickHouse schema setup entirely. |
User and database name are hardcoded in apps/api/src/db/clickhouse.ts.
OpenTelemetry
| Variable | Default | Required |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT | unset | No — leaving this unset disables OTLP export entirely (e.g. local dev without a collector) |
OTEL_SERVICE_NAME | api | No — see note below |
OTEL_RESOURCE_ATTRIBUTES | unset | No |
Note: OTEL_SERVICE_NAME's schema default is "api", but apps/api/src/observability/tracing.ts sets process.env.OTEL_SERVICE_NAME ||= "core-api" as a local-dev fallback before the schema is parsed (this module is preloaded ahead of main.ts) — docker-compose.yml sets it explicitly to api in a real deployment.
Sentry (optional)
| Variable | Default | Required |
|---|---|---|
SENTRY_DSN | unset | No — Sentry is only initialized when this is set |
Upstream providers
| Variable | Default | Required |
|---|---|---|
RAPIDAPI_KEY | "" | No |
TIK_API | "" | No — single TikHub API key backing every TikHub-proxied platform (TikTok Shop, TikTok, Reddit, Threads, LinkedIn Pro, ...); there is only one TikHub account/key, not one per platform |
Evomi proxy (optional)
| Variable | Default | Required |
|---|---|---|
PREMIUM_PROXY_PROVIDER | decodo | No |
EVOMI_DC_USER | unset | No |
EVOMI_DC_PASS | unset | No |
EVOMI_RESI_USER | unset | No |
EVOMI_RESI_PASS | unset | No |
Used by the Apify Store sweep (apps/api/src/jobs/evomi-proxy.ts): datacenter credentials first, residential as fallback. With none set, the sweep goes direct.
Vantage
| Variable | Default | Required |
|---|---|---|
VANTAGE_ENABLED | false | No — gates schema migration at boot and the background snapshot scheduler. /app routes stay mounted either way; with this off they simply have no tables to read. |
Raw response archive
| Variable | Default | Required |
|---|---|---|
RAW_ARCHIVE_ENABLED | false | No |
RAW_ARCHIVE_ZSTD_LEVEL | 12 | No — integer 1–22 |
RAW_ARCHIVE_MAX_RECORDS | 500 | No — a batch flushes when it hits this many records, or the byte limit, or the age limit, whichever comes first |
RAW_ARCHIVE_MAX_BATCH_BYTES | 4194304 (4 MiB) | No |
RAW_ARCHIVE_MAX_AGE_MS | 1800000 (30 min) | No |
RAW_ARCHIVE_MAX_BUFFER_BYTES | 67108864 (64 MiB) | No — ceiling across all per-dataset buffers combined, not per buffer |
Storage
STORAGE_PROVIDER is required with no default — it must be exactly local or cloudflare_r2 (a Zod discriminated union, apps/api/src/configs/index.ts).
| Variable | Default | Required |
|---|---|---|
STORAGE_PROVIDER | none | Yes — local or cloudflare_r2 |
LOCAL_STORAGE_DIR | .data/objects | No — used when STORAGE_PROVIDER=local |
CLOUDFLARE_ACCOUNT_ID | unset | Yes, only if STORAGE_PROVIDER=cloudflare_r2 or LLM_PROVIDER=cloudflare |
R2_ACCESS_KEY_ID | none | Yes, only if STORAGE_PROVIDER=cloudflare_r2 |
R2_SECRET_ACCESS_KEY | none | Yes, only if STORAGE_PROVIDER=cloudflare_r2 |
R2 bucket name is hardcoded in apps/api/src/storage/r2-store.ts, not configurable via env.
| Variable | Default | Required |
|---|---|---|
RESEND_EMAIL_TOKEN | unset | No — unset disables outbound email rather than failing boot; see isEmailConfigured() |
Incoming support mail additionally requires RESEND_WEBHOOK_SECRET and
EMAIL_FORWARD_TO. See support email forwarding
for the receiving webhook and activation steps.
Email uses the Resend Send Email API.
The sender domain in email.service.ts must be
verified in Resend.
That file defines separate security and onboarding senders, a default support
sender, and the reply address. Keep the reply address connected to a monitored inbox.
Read directly (not part of the validated configs/ schema)
These are read via process.env at the point of use rather than through envSchema/getEnv(), so a typo in one of these is not caught at boot the way the schema-validated variables above are.
| Variable | Default | Read in |
|---|---|---|
OTEL_SDK_DISABLED | unset | apps/api/src/observability/tracing.ts — set to "true" to force-disable the OTel SDK even if OTEL_EXPORTER_OTLP_ENDPOINT is set |
SHUTDOWN_DRAIN_MS | 20000 | apps/api/src/main.ts — how long /health reports draining before shutdown proceeds, on SIGTERM/SIGINT only |
SHUTDOWN_FORCE_EXIT_MS | 45000 | apps/api/src/main.ts — hard exit timeout if graceful shutdown hangs |
Image build manifest
GIT_COMMIT and BUILD_TIME are Docker build arguments, not runtime variables.
Each image writes them to a root-owned read-only /app/.build-info.json during
the build; /health reads that manifest. Stack environment values cannot alter
the identity reported by a running task.