# ---- App ENV=development APP_NAME=Notestack FRONTEND_URL=http://localhost:5173 API_URL=http://localhost:8000 CORS_ORIGINS=http://localhost:5173 INTERNAL_TOKEN=replace-with-random-token BILLING_ENABLED=false # Stripe. Checkout, the customer portal and webhooks. Prices are found or created by lookup key # (notestack__) from backend/app/services/plans.py; set STRIPE_PRICE_* to pin existing ones. # Webhook endpoint: {API_URL}/api/billing/webhook with events checkout.session.completed, # checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, # customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, # customer.subscription.paused, customer.subscription.resumed, invoice.paid, invoice.payment_failed, # invoice.payment_action_required, charge.dispute.created. STRIPE_SECRET_KEY= STRIPE_WEBHOOK_SECRET= STRIPE_PRICE_WRITER_MONTHLY= STRIPE_PRICE_WRITER_ANNUAL= STRIPE_PRICE_STUDIO_MONTHLY= STRIPE_PRICE_STUDIO_ANNUAL= # ---- Auth (JWT). Revocation bumps users.token_version, so tokens can be long lived. JWT_SECRET=replace-with-64-random-chars JWT_ALGORITHM=HS256 JWT_EXPIRATION_HOURS=72 JWT_REFRESH_EXPIRATION_DAYS=30 # ---- Database (also backs the job queue) # Local: a SQLite file, created by `alembic upgrade head` run from backend/. # Prod (Neon, Supabase, RDS...): Postgres with the psycopg driver, e.g. # DATABASE_URL=postgresql+psycopg://user:pass@host:5432/dbname?sslmode=require # docker-compose ignores this and points at its own Postgres container. DATABASE_URL=sqlite:///./notestack.db # ---- Google sign in (OAuth client, type Web; add http://localhost:5173 to authorized JS origins) # Client ID only: popup sign in (Google Identity Services), no redirect URIs needed. # Client ID + secret: redirect flow with server side code exchange. Add the redirect URI # (default {API_URL}/api/auth/google/callback, e.g. http://localhost:8000/api/auth/google/callback) # to "Authorized redirect URIs" on the OAuth client. Never prefix the secret with VITE_. GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= GOOGLE_REDIRECT_URI= # ---- Email (Resend). EMAIL_PROVIDER=console prints emails to the API log instead. EMAIL_PROVIDER=console RESEND_API_KEY= FROM_EMAIL=Notestack ALERTS_EMAIL=arslan@firebird-technologies.com UNSUBSCRIBE_SECRET=replace-me # Hour (UTC, 0 to 23) at which scheduled update email campaigns send their daily batch. UPDATE_EMAIL_SEND_HOUR=9 # ---- Local dev conveniences # The API runs the job worker in process when ENV=development (set false to use python -m app.worker). RUN_WORKER_IN_API= # auto = local disk (LOCAL_STORAGE_DIR) when no R2 account or endpoint is set, else R2 STORAGE_BACKEND=auto LOCAL_STORAGE_DIR=.storage # ---- Storage: Cloudflare R2 # Prod: set account id + keys, leave R2_ENDPOINT_URL empty. # Local: docker-compose points R2_ENDPOINT_URL at MinIO (keys minioadmin/minioadmin). R2_ACCOUNT_ID= R2_ACCESS_KEY_ID=minioadmin R2_SECRET_ACCESS_KEY=minioadmin R2_BUCKET=notestack-assets R2_ENDPOINT_URL= R2_PUBLIC_BASE_URL= # Folder inside the bucket when it is shared with other apps, e.g. notestack. Empty = bucket root. R2_PREFIX= # How long presigned download links stay valid, in seconds. R2_PRESIGN_TTL_SECONDS=3600 # Largest file a browser may upload (bytes). 209715200 = 200 MB. MAX_UPLOAD_BYTES=209715200 # ---- LLM (any LiteLLM model string). Defaults target Z.ai GLM-5.3 via its OpenAI compatible API. # GLM-5.x always reasons; LLM_REASONING_EFFORT (low | high | max) trades cost for depth. # Cheaper: LLM_MODEL=openai/glm-5.3-flash. Free for local dev: openai/glm-4.7-flash LLM_MODEL=openai/glm-5.3 LLM_FAST_MODEL=openai/glm-5.3-flash LLM_API_BASE=https://api.z.ai/api/paas/v4 LLM_API_KEY= LLM_TEMPERATURE=1.0 LLM_MAX_TOKENS=10000 LLM_REASONING_EFFORT=low # Fallback chat triage model when Jev is not configured. Empty = LLM_FAST_MODEL. LLM_TRIAGE_MODEL= # ---- Chat triage: TypeSafe Jev (https://typesafe.ai) # A fast classifier (typed choices, no text generation) that decides what each chat message needs # before any research runs. With a key set, greetings, off topic and app questions never reach the # LLM, and archive questions get a quick or deep research budget. Empty = LLM triage above. TYPESAFE_API_KEY= TYPESAFE_MODEL=jev-latest TYPESAFE_URL=https://api.typesafe.ai # Firecrawl (firecrawl.dev): lets Connect read sites with no RSS feed, including JavaScript sites # (Next.js, Framer, Webflow), and makes single URL imports reliable. Empty = plain HTTP fetching only. FIRECRAWL_API_KEY= # ---- Research corpus (posts as markdown files; the agent greps and reads them, no vector index) CORPUS_CACHE_DIR=.corpus-cache RESEARCH_MAX_STEPS=10 # ---- Worker (jobs queue lives in the database; no Redis) WORKER_CONCURRENCY=4 WORKER_POLL_SECONDS=1 JOB_STALE_SECONDS=900 # ---- ElevenLabs (audio overviews, video narration, voice cloning) ELEVENLABS_API_KEY= ELEVENLABS_MODEL=eleven_multilingual_v2 ELEVENLABS_VOICE_A=EXAVITQu4vr4xnSDxMaL ELEVENLABS_VOICE_B=JBFqnCBsd6RMkjVDRZzb # ---- Renderer (Remotion service for videos, quote cards and carousels: cd renderer && npm start) RENDERER_URL=http://localhost:3100 # ---- Launchpad social publishing # Bluesky needs nothing here (users connect with an app password). # X: developer app with OAuth 2.0, type Web App, scopes tweet.read tweet.write users.read offline.access. # Callback URI: {API_URL}/api/social/x/callback e.g. http://localhost:8000/api/social/x/callback # LinkedIn: app with the "Sign In with LinkedIn using OpenID Connect" and "Share on LinkedIn" products. # Redirect URL: {API_URL}/api/social/linkedin/callback X_CLIENT_ID= X_CLIENT_SECRET= LINKEDIN_CLIENT_ID= LINKEDIN_CLIENT_SECRET= # Fernet key for stored social tokens; derived from JWT_SECRET when empty. SOCIAL_TOKEN_KEY=