Saylent

Environment variables

Every setting the full app reads - required for, default, what breaks without it.

Copy .env.example to .env.local (local/cloud) or .env (own-server, Docker Compose) and fill in what your target needs. Check what's missing for either tier at any time:

npm run check-env -- --profile cli     # what `npx saylent audit` needs
npm run check-env -- --profile app     # what the full app needs

Required

VariableRequired forWhat breaks without it
NEXT_PUBLIC_APP_URLevery targetLinks the app generates (emails, share links) point at the wrong place
NEXT_PUBLIC_SITE_URLevery targetLayout, sitemap, and robots.txt use the wrong origin
NEXT_PUBLIC_APP_NAMEevery targetFalls back to "Saylent" - the product name shown in the UI, emails, and the Inngest app id
NEXT_PUBLIC_SUPABASE_URLevery targetThe app can't reach your database at all
NEXT_PUBLIC_SUPABASE_ANON_KEYevery targetThe publishable key from the Publishable and secret API keys tab (sb_publishable_...) - not the legacy tab
SUPABASE_SERVICE_ROLE_KEYevery targetThe secret key from the same tab (sb_secret_...) - admin actions and the audit pipeline write through this
DATABASE_URLevery targetThe Transaction pooler string (port 6543) - the app's own runtime queries
DIRECT_DATABASE_URLevery targetThe Direct connection string (port 5432) - migrations only; using the pooler here hangs
INNGEST_EVENT_KEYcloud, own-server (production)Local dev needs neither Inngest key - npx inngest-cli dev reads none
INNGEST_SIGNING_KEYcloud, own-server (production)Required so /api/inngest is authenticated; without it in production, paid runs are externally triggerable. Not needed on a read-only demo deployment (NEXT_PUBLIC_DEMO_READONLY=1), which runs nothing.
OPENAI_API_KEY / ANTHROPIC_API_KEYevery targetOne of the two is enough to run an audit; both give cross-family judging

Optional

VariableDefaultWhat it's for
GEMINI_API_KEY / PERPLEXITY_API_KEYunsetTwo more answer engines. Gemini's free tier answers unreliably - turn on billing
NEXT_PUBLIC_OPERATOR_NAMEunsetThe legal entity or person operating this deployment, printed on /privacy and /terms. Unset, those two pages refuse to render a policy and say so - they never publish one attributed to nobody
NEXT_PUBLIC_CONTACT_EMAILhello@example.comCorrection/support address on public pages and error pages
APP_SECRETunsetThe pgcrypto passphrase that encrypts provider API keys saved from Providers and models in the operator console, so an operator can change a key or a per-role model with no redeploy. Generate with openssl rand -hex 32 (min 16 chars). Unset = keys are environment-only and the console says so; models stay editable either way. Never stored in the database - losing or changing it makes every console-saved key unreadable
RESEND_API_KEY + EMAIL_FROMunsetTransactional email - inert without both. Two emails send today: report-ready when an audit finishes, and a day-10 verify reminder (skipped when FLAG_SCHEDULED_RUNS is on, or the brand has already been verified)
INNGEST_DEVunsetForces the Inngest SDK's local-dev mode outside its own auto-detection
SENTRY_DSN / NEXT_PUBLIC_SENTRY_DSNunsetServer and browser error tracking - every Sentry variable is inert until SENTRY_DSN is set
SENTRY_AUTH_TOKEN, SENTRY_ORG, SENTRY_PROJECTunsetSource-map upload at build time (read by the Sentry build plugin)
SENTRY_TRACES_SAMPLE_RATE0.2 in prod, 0 elsewherePerformance trace sampling, 0–1
DEMO_RUN_IDunsetPoints the sample report at a real consented run instead of the shipped illustrative sample
BRAND_LIMIT5Brand-count cap per account (no separate plan tiers)
RUN_THROTTLE_WINDOW_HOURS / RUN_THROTTLE_AUDITS / RUN_THROTTLE_VERIFIES24 / 3 / 3Rolling-window throttle on manual runs, per brand
DAILY_SPEND_CAP_USD20Global daily provider-spend ceiling - crossing it trips the kill switch
FLAG_EMAILSonKill switch for transactional email, independent of whether RESEND_API_KEY is set
AUDIT_PROFILEfullThe app runs the full profile (23 questions, about $3.70 to $5.50 per run with four engines) and shows the estimate before every run. smoke (6 questions) is a development profile and is refused for real users in production
FLAG_SCHEDULED_RUNSoffThe weekly-verify and monthly-audit schedules. Off by default so nothing spends your keys unattended; set 1 when you want scheduled runs. Once on, a brand is eligible as soon as it has one completed audit. The pre-rename name is still read as an alias for one release (LEGACY_ENV_ALIASES in src/lib/flags.ts)
FLAG_CSP_ENFORCEonEnforced Content-Security-Policy vs. Report-Only fallback
FLAG_COVE_AUDIToffAn extra evidence-check pass over each drafted fix (one more model call per artifact when on)
MODEL_* (ten variables)the shipped registryOverride which model serves which role - see Configuration
SAYLENT_USER_AGENTSaylentAudit/<version> (+https://yotambraun.github.io/saylent/docs/crawler)Your crawler's own identity - set this to something that traces back to you
SAYLENT_APP_DOMAINunsetYour domain, appended to the diagnostic UA the live per-bot probe sends

Own-server (Docker) only

VariableDefaultWhat it's for
NEXT_OUTPUT_STANDALONEunsetSet to 1 only when building the Docker image - enables Next's standalone output. Vercel and local dev never set this
INNGEST_BASE_URLunsetPoints the app at the self-hosted Inngest container (http://inngest:8288 in docker-compose.yml) instead of Inngest Cloud
APP_PORT / INNGEST_PORT3000 / 8288Which host ports Compose exposes
NEXT_PUBLIC_AUTH_METHODSpassword,magic-linkWhich login methods the sign-in page shows (add google if you've configured that provider)

Hosted read-only demo (optional)

Leave every one of these empty on a normal deployment - unset means the demo does not exist and nothing else changes.

VariableDefaultWhat it's for
NEXT_PUBLIC_DEMO_READONLYunset1 turns the whole deployment into a public read-only demo: an unauthenticated visitor to /app is signed in as the shared demo account below, a banner says so, and every write (settings, brands, fixes, shares, support, account delete/export, and createRun - so every LLM call) is refused. $0 per visitor
DEMO_USER_EMAIL / DEMO_USER_PASSWORDunsetThe shared demo account the proxy signs visitors in as. Create or repair it with npx tsx --tsconfig scripts/tsconfig.json scripts/seed-demo.ts, which also seeds the Kestrel Uptime fixture as its brand and finished run. Server-only; use a throwaway account on a throwaway Supabase project - it is demoted to role=user on every seed
NEXT_PUBLIC_DEMO_DOCS_URLthe public docs siteWhere the demo banner's "Read the docs" link points

The docs site's own site check

One more variable, read only by the public docs/marketing site's build (website/, a separate workspace from the app above), not by the self-hosted app:

VariableDefaultWhat it's for
NEXT_PUBLIC_INSTANT_CHECK_URLunsetThe base URL of the deployed services/instant-check service. Set this after deploying that service - the docs site's home-page widget degrades to printing the saylent gate-check command instead of a broken input when it's unset, so a fork or a local npm run site:dev is never wrong about what works

The platform variables

HOME / USERPROFILE are read by the CLI's own key store to find your per-OS config directory - you never set these. NODE_ENV, NEXT_RUNTIME, and VERCEL_ENV are set by the platform, not by you. SKIP_ENV_VALIDATION lets a build run without every required variable present (used by CI and the Docker build, which must build without secrets); don't set it for a real deployment.

On this page