# Assemblix — single env file for backend + frontend (VITE_*) + Docker Compose.
# Copy to .env at the repo root and fill it in (.env is gitignored): cp .env.example .env
# DATABASE_URL / REDIS_URL use HOST values; compose overrides them to service hostnames.
# --- Docker Compose ---
COMPOSE_PROFILES= # service profiles; empty=core only. "queue"=Redis+worker, add "scale"=PgBouncer
POSTGRES_USER=assemblix
POSTGRES_PASSWORD=assemblix # CHANGE for any non-local deploy
POSTGRES_DB=assemblix
POSTGRES_PORT=5412 # host DB port (dev binds 127.0.0.1 only; prod doesn't publish it)
API_PORT=8000 # host port for the api container
WEB_PORT=8080 # host port for the web container
# --- Database ---
DATABASE_URL=postgresql://assemblix:assemblix@localhost:5412/assemblix # HOST value; compose points it at `postgres`
DB_POOL_SIZE=10 # persistent connections per process
DB_MAX_OVERFLOW=20 # extra burst connections above pool size
DB_POOL_TIMEOUT=30 # seconds to wait for a free connection
DB_DISABLE_STATEMENT_CACHE=false # true only behind a transaction-mode pooler (PgBouncer/Supavisor)
DB_TARGET_HOST=postgres # in-network DB host for api/worker; set `pgbouncer` on scale tier
DB_TARGET_PORT=5432 # in-network DB port; 6432 for PgBouncer
PGBOUNCER_MAX_DB_CONNECTIONS=50 # scale tier: real backend conns to Postgres (keep < max_connections)
PGBOUNCER_MAX_CLIENT_CONN=1000 # scale tier: client conns the pooler accepts
PGBOUNCER_DEFAULT_POOL_SIZE=25 # scale tier: default pool size
# --- Security (REQUIRED — app won't start without these two) ---
JWT_SECRET_KEY= # JWT signing secret, min 32 chars. gen: python -c "import secrets;print(secrets.token_urlsafe(48))"
JWT_ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=1440 # access token lifetime, minutes (1440=24h)
ENCRYPTION_KEY= # Fernet key for stored creds. gen: python generate_encryption_key.py
# --- CORS & limits ---
CORS_ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000 # comma-separated, no trailing slash
APP_PUBLIC_URL= # public web app URL for links in notifications; blank = first CORS origin
HTTP_NODE_ALLOW_INTERNAL=false # allow HTTP-request node to reach private IPs (SSRF opt-out)
WORKFLOW_EXECUTION_TIMEOUT_SECONDS=1800 # wall-clock budget per workflow run
CEL_EVALUATION_TIMEOUT_SECONDS=5 # per-expression CEL eval timeout
KB_MAX_UPLOAD_BYTES=26214400 # knowledge-base max upload size (25 MB)
KB_MAX_PDF_PAGES=500 # knowledge-base max PDF pages
KB_PDF_PARSE_TIMEOUT_SECONDS=30 # knowledge-base PDF parse timeout
# --- Billing / Payments (Enterprise; off by default) ---
BILLING_ENABLED=false # false = /payments router not mounted
PAYMENT_PROVIDER=paddle
PADDLE_API_KEY=
PADDLE_WEBHOOK_SECRET=
PADDLE_ENVIRONMENT=sandbox
PADDLE_PRICE_PRO=
PADDLE_PRICE_BUSINESS=
# One-off credit packs. A pack is listed by GET /billing/packs — and therefore
# purchasable — only while its price id is set here.
PADDLE_PRICE_PACK_S=
PADDLE_PRICE_PACK_M=
PADDLE_PRICE_PACK_L=
# --- LLM providers ---
# Transport base URL per provider, for chat completions and transcription.
# Leave empty for the provider's own endpoint.
OPENAI_API_BASE_URL=https://api.openai.com/v1
GEMINI_API_BASE_URL=
# Realtime (speech-to-speech) endpoints for Voice Agents. Separate on purpose: a
# REST gateway is not automatically a WebSocket gateway, and an LLM proxy that
# serves /v1/chat/completions answers the realtime route with 404. Leave empty to
# reach the provider directly; set only once a gateway is known to proxy these:
# OpenAI wss:///realtime
# Gemini wss:///ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent
OPENAI_REALTIME_BASE_URL=
GEMINI_LIVE_BASE_URL=
SYSTEM_OPENAI_API_KEY= # system keys for FREE plan / fallback; blank = self-host BYO-key
SYSTEM_GEMINI_API_KEY=
SYSTEM_DEEPSEEK_API_KEY=
# --- Voice output (TTS, END node) ---
SYSTEM_ELEVENLABS_API_KEY= # platform ElevenLabs key for hosted voice; blank = self-host BYO-key
ELEVENLABS_API_BASE_URL=https://api.elevenlabs.io/v1 # override to route ElevenLabs via a proxy
TYPESAFE_API_BASE_URL=https://api.typesafe.ai # Jev node; override to route TypeSafe via a proxy
VOICE_OUTPUT_MAX_CHARS=2000 # hard cap on chars synthesized per END node (over → text-only)
# --- AI avatars (anam.ai, bitHuman, LiveAvatar) — BYO key per project via credentials; base URL overrides only ---
ANAM_API_BASE_URL=https://api.anam.ai
BITHUMAN_API_BASE_URL=https://api.bithuman.ai
BITHUMAN_RUNTIME_URL=https://auth.api.bithuman.ai/v1/runtime-tokens/request
LIVEAVATAR_API_BASE_URL=https://api.liveavatar.com
LIVEAVATAR_SANDBOX=false # true = free ~1-minute sandbox sessions (dev only)
# --- Yandex SpeechKit (voice input + output) — needs an API key AND a folder id ---
SYSTEM_YANDEX_SPEECHKIT_API_KEY= # platform SpeechKit key for hosted voice; blank = self-host BYO-key
SYSTEM_YANDEX_SPEECHKIT_FOLDER_ID= # platform folder id (required together with the key above)
YANDEX_TTS_API_BASE_URL=https://tts.api.cloud.yandex.net/speech/v1 # override to route via a proxy
YANDEX_STT_API_BASE_URL=https://stt.api.cloud.yandex.net/speech/v1 # override to route via a proxy
# --- Voice-agent avatars (LiveKit; optional, all four or none) ---
LIVEKIT_URL= # URL the API dials, e.g. wss://rtc.example.com (or http://livekit:7880 in compose)
LIVEKIT_PUBLIC_URL= # URL for browsers AND avatar vendors: public wss:// with valid TLS
LIVEKIT_API_KEY= # LiveKit API key
LIVEKIT_API_SECRET= # LiveKit API secret (>= 32 chars). gen: openssl rand -base64 48
AVATAR_JOIN_TIMEOUT_SECONDS=45 # wait for caller mic + avatar video before failing the call
# --- Tools ---
TAVILY_API_KEY= # web search; free key at https://tavily.com
# --- OAuth (blank = provider hidden) ---
GOOGLE_OAUTH_CLIENT_ID=
GOOGLE_OAUTH_CLIENT_SECRET=
GITHUB_OAUTH_CLIENT_ID=
GITHUB_OAUTH_CLIENT_SECRET=
# --- Credit system (USD) ---
CREDIT_VALUE_USD=0.0001
CREDIT_MARGIN_PERCENT=10
REQUEST_FEE_USD=0.0001
VOICE_PLATFORM_FEE_USD_PER_MINUTE=0.02
# --- Misc ---
HOST_URL=http://localhost:8000
TELEGRAM_API_BASE_URL= # custom Telegram Bot API base (proxy); blank = api.telegram.org
TASK_TIMEOUT_SECONDS=60 # foreground exec timeout before falling back to background task
# --- Redis / Queue (optional; blank = single-process Postgres-only self-host) ---
REDIS_URL= # set to enable queue/pubsub/checkpointing. docker queue tier: redis://redis:6379/0
EXECUTION_QUEUE_ENABLED=false # Arq distributed execution queue (needs REDIS_URL)
DEBUG_EVENTS_USE_REDIS=false # publish debug events to Redis Pub/Sub (needs REDIS_URL)
EXECUTION_CHECKPOINTING_ENABLED=false # persist checkpoints to Redis for crash resume (needs REDIS_URL)
GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS=30 # seconds to drain in-flight execs on SIGTERM
LOGIN_RATE_LIMIT_PER_5MIN=10 # max login/register attempts per 5 min per IP before 429
# --- Observability ---
METRICS_ENABLED=true # enable /metrics endpoint + worker scrape server
METRICS_PORT=9000 # worker Prometheus scrape port
# --- Horizontal scaling (safe defaults for self-host) ---
WORKER_MAX_JOBS=10 # concurrent workflow jobs per Arq worker (workflows are I/O-bound)
WEB_CONCURRENCY=1 # uvicorn worker processes for the API tier
# PROMETHEUS_MULTIPROC_DIR= # empty writable dir for cross-process metrics; needed when WEB_CONCURRENCY>1
WORKER_REPLICAS=1 # worker container replicas (read by compose `worker` service)
WORKER_METRICS_ENABLED=true # worker exposes its scrape port; false to avoid bind clashes on multi-replica
ORG_MAX_CONCURRENCY=0 # cross-worker LLM concurrency cap per org; 0=unlimited (needs REDIS_URL)
PROVIDER_MAX_CONCURRENCY=0 # cross-worker LLM concurrency cap per provider; 0=unlimited (needs REDIS_URL)
CONCURRENCY_ACQUIRE_TIMEOUT_SECONDS=30 # wait for a free slot before proceeding anyway
# --- Frontend (VITE_* — only these reach the browser bundle) ---
VITE_API_BASE_URL=/api # SPA API base; keep /api (nginx/Vite proxy forward it)
VITE_APP_URL= # public app URL for API code samples; blank = current browser host
VITE_APP_VERSION=1.0.0
VITE_LANGS=en,es,ru # locales to build; the two below are runtime hints
VITE_DEFAULT_LANGUAGE=en
VITE_SUPPORTED_LANGUAGES=en,es,ru
VITE_GOOGLE_CLIENT_ID= # OAuth client IDs (public); blank to hide
VITE_GITHUB_CLIENT_ID=
VITE_YANDEX_CLIENT_ID=
VITE_BILLING_ENABLED=false # true = show billing/payment UI (Enterprise); false for self-host
VITE_PADDLE_CLIENT_TOKEN= # Paddle billing (Enterprise); blank for self-host
VITE_PADDLE_ENVIRONMENT=production