Web (web/.env)
Fed to the Next.js app. Variables prefixed with NEXT_PUBLIC_ are exposed to the browser.
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | ✅ | Your Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY | ✅ | Supabase anon key (public, RLS-protected) |
SUPABASE_SERVICE_ROLE_KEY | ✅ | Service role key (bypasses RLS — used for server actions; never expose) |
NEXT_PUBLIC_API_URL | ✅ | Server URL (e.g. https://api.your-domain.com or http://localhost) |
NEXT_PUBLIC_APP_URL | ✅ | Web app URL (used for OAuth callback URLs). For apps to sign in to the MCP server, it must be https:// with no path, such as https://ansvisor.mycompany.com (or http://localhost while developing): it is the authorization server’s issuer, and the /.well-known addresses sit at its root. On plain http elsewhere, app sign-in stays off and clients use API keys. |
OPENAI_API_KEY | optional* | OpenAI API key |
GOOGLE_GEMINI_API_KEY | optional* | Google AI Studio Gemini key |
ANTHROPIC_API_KEY | optional* | Anthropic Claude key |
PERPLEXITY_API_KEY | optional* | Perplexity API key |
GROK_API_KEY | optional* | xAI Grok key |
NEXT_PUBLIC_IS_CLOUD | optional | "true" for the official cloud build (enables billing UI). Leave unset for self-hosted (all features unlocked). |
STRIPE_SECRET_KEY | cloud only | Stripe live/test secret key |
STRIPE_WEBHOOK_SECRET | cloud only | Stripe webhook signing secret |
CHAT_AGENTIC | optional | "true" turns on the new Chat at /dashboard/chat and Agent Center. Links to /dashboard/agent then open Chat. Default: off (unset, or any other value), and the Agent stays at /dashboard/agent as before. The server reads it too: set the server’s CHAT_AGENTIC to the same value. With it on, also set CRON_SECRET on both sides, or Chat can’t answer. A change applies after a restart (self-hosted) or a new deployment (Vercel). |
CHAT_AGENTIC_ORGS | optional | With CHAT_AGENTIC=true: turns the new Chat on only for these workspaces (organization ids, comma-separated, any case). Default: empty, which means every workspace. Does nothing while CHAT_AGENTIC is off. Set the server’s CHAT_AGENTIC_ORGS to the same list. A change applies after a restart or a new deployment. |
CHAT_AUTO_GO_LIVE | optional | "true" lets agents publish and send pages on their own: it shows that switch in Settings › Chat and its box on an agent’s form. Works only where the new Chat is on (CHAT_AGENTIC). Default: off, so the switch is hidden and no agent goes live on its own. Even when on, nothing goes live until an Admin turns the switch on (it needs Content Quality, NEXT_PUBLIC_CONTENT_QUALITY_PHASE 1 or higher) and allows each agent on its form. The server reads it too: set the server’s CHAT_AUTO_GO_LIVE to the same value, or the server refuses every step and each page waits for a click. A change applies after a restart or a new deployment. |
CHAT_AUTO_GO_LIVE_ORGS | optional | With CHAT_AUTO_GO_LIVE=true: allows it only for these workspaces (organization ids, comma-separated, any case). Default: empty, which means every workspace the new Chat is on for. Set the server’s CHAT_AUTO_GO_LIVE_ORGS to the same list. A change applies after a restart or a new deployment. |
ADS_ENABLED | internal preview | Ads is in development: leave unset in production. "true" turns on the Ads area for every workspace on the install, for local development and preview deployments: Ads under Analytics on the menu, with a “New” pill, and its page at /dashboard/ads. Default: off (unset, or any other value): Ads is on no menu and its page is not found. There is no per-workspace or per-person switch. Web only: the server doesn’t read it. A change applies after a restart or a new deployment. |
STAFF_EMAILS | optional | Sign-in e-mails of the people who get Chat’s staff tools (comma-separated, any case; the e-mail must be confirmed). With the new Chat on, they get Debug in a chat’s ⋯ menu, which shows each answer’s tools, inputs, outputs, tokens and cost, and Chat’s design preview at /dashboard/chat/preview. Default: empty, which means nobody. Each Debug view is logged. Web only, and never sent to the browser. A change applies after a restart or a new deployment. |
CRON_SECRET | cloud; self-host with Chat | Shared secret the web app sends to the server’s internal endpoints: Vercel cron, and Chat’s AI budget for each model step when CHAT_AGENTIC is on. Self-hosted with Chat on, set it here and in the server’s .env to the same value, or Chat can’t answer. |
CHAT_INTERNAL_SECRET | optional | With the new Chat on (CHAT_AGENTIC=true): the secret the server sends to this app’s /api/internal/chat/agents/* endpoints, which fire agent schedules and Signal triggers and sweep agent runs. Same value as the server’s. Unset: agents run only with Run now. Generate with openssl rand -hex 32 |
CHAT_ACTING_SECRET | optional | With the new Chat on (CHAT_AGENTIC=true): the secret this app sends to the API server, with X-Acting-User: <member id>, for an agent’s triggered run’s writes there (an opportunity’s status, a dismissed prompt suggestion, Send to Action Center and its duplicate check), which have no one’s session. A secret of its own, not CHAT_INTERNAL_SECRET’s value; same value as the server’s. At least 32 characters; unset or shorter: those changes ask first, on a card in the owner’s Inbox. If the web’s and the server’s values differ, the server answers 401 and the step stops with acting_refused: the change then waits on that card too, and the owner’s Approve makes it with their own session. Generate with openssl rand -hex 32 |
MCP_OAUTH_ENABLED | optional | App sign-in for the MCP server: Claude, ChatGPT and other MCP apps connect to /api/mcp by signing the person in (OAuth), instead of a pasted API key. On by default wherever NEXT_PUBLIC_APP_URL is https:// with no path. false switches it off (the kill switch; 0, off and no too): the /.well-known OAuth addresses and /api/oauth/* answer 404, the MCP endpoint’s 401 no longer starts a sign-in, app tokens are refused, and Connected apps is hidden in Settings. Connections made before stay stored: once it is back on, an app picks up again with its refresh token, within that token’s 30 days. API keys work either way. A change applies after a restart or a new deployment. |
OPENAI_APPS_CHALLENGE | optional | The domain verification token OpenAI’s portal gives for listing the app in ChatGPT. Served as plain text, exactly as set (surrounding spaces and newlines left out), at /.well-known/openai-apps-challenge on the app’s root, with Cache-Control: no-store. Unset: that address answers 404. Needed only to list your instance in ChatGPT. A change applies after a restart or a new deployment. |
STAFF_EMAILS | optional | Comma-separated sign-in e-mails of Ansvisor staff: Chat’s Debug view and the free report cards’ admin view. Unset: nobody. |
| REPORT_CARD_DAILY_CAP, REPORT_CARD_MONTHLY_BUDGET_USD | cloud only | The free report card’s caps as the admin view shows them; set them only if the server’s differ from 200 cards a day and $200 a month (Free report card) |
* You need at least one AI provider key for tracking to work. Configure providers per-brand in the dashboard.
Server (server/.env)
Fed to the Express API + workers.
| Variable | Required | Description |
|---|---|---|
NODE_ENV | ✅ | production for live, development for local |
PORT | ✅ | Default 80 |
SUPABASE_URL | ✅ | Same as web’s NEXT_PUBLIC_SUPABASE_URL |
SUPABASE_ANON_KEY | ✅ | Supabase anon key |
SUPABASE_SERVICE_ROLE_KEY | ✅ | Service role key (used by workers and admin endpoints) |
OPENAI_API_KEY | optional* | OpenAI key |
ANTHROPIC_API_KEY | optional* | Anthropic key |
GOOGLE_GENERATIVE_AI_API_KEY | optional* | Google Gemini key (server-side name differs from web) |
OPENROUTER_API_KEY | optional | OpenRouter key: one key for many vendors’ models. Lets the analysis tasks (such as suggestions, Site Audit signals and sentiment) use model ids that start with openrouter/, e.g. openrouter/anthropic/claude-haiku-4.5. It can’t run prompt tracking, which needs each provider’s own web search. With it set and SENTIMENT_MODEL unset, brand sentiment is answered by a cheaper decision model through OpenRouter, falling back to openai/gpt-5-mini. Requests ask OpenRouter for providers that don’t keep or train on the data. Default: unset. A change applies after a restart. |
DEFAULT_SUGGESTION_MODEL | optional | Default LLM for AI suggestions (default: google/gemini-3-flash-preview) |
TOPIC_SUGGESTION_MODEL | optional | LLM for topic suggestions |
PROMPT_SUGGESTION_MODEL | optional | LLM for prompt suggestions |
COMPETITOR_SUGGESTION_MODEL | optional | LLM for competitor suggestions |
TAVILY_API_KEY | optional | Runs the web search for topic and competitor suggestions through Tavily instead of the model provider’s own search tool (useful when a free provider key’s search is quota-limited). Each search uses a Tavily credit plus one extra model call. Without it, a quota-limited search falls back to research from the model’s own knowledge. |
CONTENT_READ_MODEL | optional | LLM the Content Editor reads and judges with: the brief (Rebuild outline) and Quality’s checks. Default: anthropic/claude-haiku-5-5 when ANTHROPIC_API_KEY is set, else IMPROVE_SUGGESTION_MODEL, then DEFAULT_SUGGESTION_MODEL, then google/gemini-3-flash-preview. QUALITY_CHECK_MODEL, when set, still picks Quality’s model. |
CONTENT_WRITE_MODEL | optional | LLM the Content Agent writes with: pages written or rewritten whole and its suggestions. Default: anthropic/claude-sonnet-5-5 when ANTHROPIC_API_KEY is set, else the same fallbacks as CONTENT_READ_MODEL. A Claude model must support structured outputs (Claude 4.5 models or newer). |
AUDIT_LLM_MODEL | optional | LLM for the Site Audit’s LLM-judged signals + AI recommendations. Use a pinned, versioned id, not a -preview or -latest alias: each signal’s verdict is kept by model id and judged again only when the page or the model changes. Default: google/gemini-3.8-flash for the signals, google/gemini-3-flash-preview for the recommendations |
ALLOWED_ORIGINS | ✅ | Comma-separated list of web origins allowed to call the API (for CORS) |
IS_CLOUD | optional | "true" only for the official cloud build |
DATAFORSEO_LOGIN | optional | DataForSEO login (for prompt volume analysis) |
DATAFORSEO_PASSWORD | optional | DataForSEO password |
DEMAND_SIGNALS | optional | "true" to read each tracked prompt’s Google Trends, People Also Ask and YouTube demand from DataForSEO about once a week (the Demand card on a prompt’s page). Costs money: each read is a paid DataForSEO task, about $0.004 a prompt a week, at most 500 prompts a night. Off by default; needs the DataForSEO keys above. Search Console demand is read every night either way, at no cost. |
CLORO_API_KEY | optional | Cloro web scraper API key (for ChatGPT/Gemini/Google AI web scraping) |
CLORO_CHATGPT_FANOUT | optional | ChatGPT’s query fan-out: sample asks for each prompt’s searches one day a week, all on every nightly run, off never. Default off on self-host (an unknown value is off too, with a warning in the log). It goes through Cloro’s legacy ChatGPT interface, which Cloro calls best-effort and temporary. Only the nightly run’s first attempt asks; manual runs and retries don’t. Costs credits: +2 for each ChatGPT task that asks, and a prompt has one task per location, so sample adds 2 credits a week for each prompt and location. The day is the UTC day the nightly run starts: with DAILY_CRON_SCHEDULE close to midnight UTC, a prompt can be asked 0 or 2 times in a week |
PLATFORM_PROVIDER | optional | Default cloro. Used to pick scraper backend |
SCRAPEDO_API_KEY | Site Audit | Scrape.do API key — required to run Site Audit (it fetches target pages via Scrape.do’s proxy + JS render). Distinct from CLORO_API_KEY, which scrapes the AI answer engines. |
AI_VOLUME_MULTIPLIER | optional | Estimated AI search adoption rate (default 0.45 = 45% of a prompt’s Google volume, the median of its keywords’ monthly searches; AI Overviews appear on roughly 43–48% of Google searches) |
CHAT_AGENTIC | optional | "true" turns on the server’s side of the new Chat: Chat’s AI budget, the agents’ minute tick (with CHAT_INTERNAL_SECRET), daily suggestions, the impact ledger and the daily sync of model prices. Default: off. Set the web’s CHAT_AGENTIC to the same value. A change applies after a restart or a new deployment. |
CHAT_AGENTIC_ORGS | optional | With CHAT_AGENTIC=true: turns the new Chat on only for these workspaces (organization ids, comma-separated, any case). Default: empty, which means every workspace. Set the web’s CHAT_AGENTIC_ORGS to the same list. A change applies after a restart or a new deployment. |
CHAT_AUTO_GO_LIVE | optional | With the new Chat on (CHAT_AGENTIC=true): "true" lets the server carry out the go-live steps the web app asks for when an agent publishes or sends a page on its own. Default: off: the server refuses every such step (403 auto_go_live_off, logged), even for a workspace whose Admins left Agents that publish on their own on, and the page waits for a click. Set the web’s CHAT_AUTO_GO_LIVE to the same value. A change applies after a restart or a new deployment. |
CHAT_AUTO_GO_LIVE_ORGS | optional | With CHAT_AUTO_GO_LIVE=true: allows it only for these workspaces (organization ids, comma-separated, any case). Default: empty, which means every workspace the new Chat is on for. Set the web’s CHAT_AUTO_GO_LIVE_ORGS to the same list. A change applies after a restart or a new deployment. |
CRON_SECRET | cloud; self-host with Chat | Shared secret with web’s CRON_SECRET (the same value); protects the internal endpoints: cron, and Chat’s AI budget when CHAT_AGENTIC is on |
CHAT_INTERNAL_SECRET | optional | With the new Chat on (CHAT_AGENTIC=true): every minute the server calls the web app (PUBLIC_APP_URL) to fire agent schedules and sweep agent runs, and after each signal pass sends it the new signals for Signal triggers. Same value as the web’s. Unset: agents run only with Run now. Not in NODE_ENV=development; there, run node src/scripts/chat-agents-tick.js |
CHAT_ACTING_SECRET | optional | With the new Chat on (CHAT_AGENTIC=true): an agent’s triggered run has no one’s session, so the web app’s writes through this API for it (an opportunity’s status, a dismissed prompt suggestion, Send to Action Center and its duplicate check) send this secret with X-Acting-User: <member id>, checked as that member’s own session would be (their workspace with Chat on, their brand access, their role), on those routes only; a request naming a member without this secret is refused before Supabase Auth. A secret of its own, not CHAT_INTERNAL_SECRET’s value; same value as the web’s. At least 32 characters; unset or shorter: those writes are refused. Generate with openssl rand -hex 32 |
COMPOSIO_API_KEY | optional | Composio API key for the sign-ins in Settings › Integrations: Google Search Console, Google Analytics, Google Merchant Center, Send to CMS’s Webflow, Slack and HubSpot. Each one also needs its own auth config id below; without the key and its id, that card says “not configured” and everything else works. Composio Cloud keeps the connection tokens (see the note under this table). |
COMPOSIO_GSC_AUTH_CONFIG_ID | optional | The auth config id of Composio’s Google Search Console toolkit. With COMPOSIO_API_KEY, turns on the Search Console card. |
COMPOSIO_GA_AUTH_CONFIG_ID | optional | The auth config id of Composio’s Google Analytics toolkit. With COMPOSIO_API_KEY, turns on the Google Analytics card. |
COMPOSIO_GMC_AUTH_CONFIG_ID | optional | The auth config id of Composio’s Google Merchant toolkit, set up with your own Google OAuth app and the https://www.googleapis.com/auth/content scope (Composio has no managed app for it). The catalog reads through Composio’s proxy, so the API key needs the “Proxy execute” permission. With COMPOSIO_API_KEY, turns on the Merchant Center card. |
COMPOSIO_SLACK_AUTH_CONFIG_ID | optional | The auth config id of Composio’s Slackbot toolkit: your own Slack app (its client id and secret, with the bot scopes chat:write, chat:write.public, channels:read, groups:read, users:read and users:read.email), or Composio’s managed app, whose consent screen says Composio. Each brand connects a Slack workspace and picks a channel; Composio keeps the bot token. Slack is called through Composio’s proxy, so the API key needs the “Proxy execute” permission. With COMPOSIO_API_KEY, turns on the Slack card. |
COMPOSIO_WEBFLOW_AUTH_CONFIG_ID | optional | The auth config id of Composio’s Webflow toolkit with API-key auth. Each brand connects its own site token, which Composio keeps. With COMPOSIO_API_KEY, turns on Send to CMS’s Webflow card. |
COMPOSIO_HUBSPOT_AUTH_CONFIG_ID | optional | The auth config id of Composio’s HubSpot toolkit with OAuth (Composio’s managed app, or your own HubSpot app). Each brand connects its own HubSpot account; Ansvisor calls HubSpot through Composio’s proxy, so the API key needs the “Proxy execute” permission. Scopes: oauth, crm.objects.contacts.read, crm.objects.contacts.write, crm.schemas.contacts.read, crm.schemas.contacts.write, crm.objects.deals.read and content. With COMPOSIO_API_KEY, turns on the HubSpot card. |
COMPOSIO_GSC_TOOLKIT_VERSION, COMPOSIO_GA_TOOLKIT_VERSION, COMPOSIO_WEBFLOW_TOOLKIT_VERSION | optional | Pin the Composio toolkit versions the tools run on. Defaults: 20260721_00 (Search Console and Analytics) and 20260826_00 (Webflow). |
COMPOSIO_GA_MAX_PROPERTIES, COMPOSIO_GA_STREAM_LOOKUPS | optional | Google Analytics property listing limits: how many properties are listed (default 500) and how many of them have their web streams looked up to match the brand’s domain (default 100; the rest stay listed to pick by hand). |
CONNECTOR_CREDENTIALS_KEY | optional | The key that encrypts the sign-ins Ansvisor keeps itself: a catalog store’s, a WordPress site’s, and a Framer project’s or a Wix site’s API key. 32 bytes in base64 (openssl rand -base64 32); only the server holds it. Without it, Settings says store connections, WordPress, Framer and Wix aren’t set up, and feeds, uploads and the webhook work as before. Changing it makes the saved sign-ins unreadable until they’re entered again. |
TURNSTILE_SECRET_KEY | cloud only | Cloudflare Turnstile’s secret for the free report card form’s bot check. Without it a cloud server refuses every request (Free report card) |
REPORT_CARD_* | optional | The free report card’s origins, caps, model, prices and sender: see Free report card. The cards run on the cloud only. |
POSTHOG_PERSONAL_API_KEY | optional | A PostHog personal API key with only the query:read scope, for the project in POSTHOG_PROJECT_ID. With both set, the team’s e-mail when a customer cancels (or asks for three days) in Settings › Billing › Cancel Subscription is followed in the same thread, after the customer has their answer, by an analysis of the person’s last 14 days: each day’s pages (ids shown as :id) and product events, where they got stuck (upgrade walls, errors, unfinished flows, rage and dead clicks), the sessions to watch with links to their replays, what the workspace never used (from your database), and the AI’s take in Turkish on why they may have cancelled and what to fix, on CONTENT_READ_MODEL’s model. The AI gets the reasons, the customer’s message, the plan, and pages, event names and counts; never the organization’s or the person’s name, their e-mail address, a domain or a link. Read only when CANCEL_FEEDBACK_EMAILS is set. PostHog failing or taking over 10 seconds: the follow-up goes without the activity; the AI failing or taking over 20 seconds: without its part. Neither changes the first e-mail or the answer kept. Default: unset, which means nothing is read and no follow-up is sent. |
POSTHOG_PROJECT_ID | optional | The PostHog project the analysis above is read from (PostHog’s project settings). Needed with POSTHOG_PERSONAL_API_KEY. |
POSTHOG_APP_HOST | optional | PostHog’s app, where its API is, e.g. https://eu.posthog.com. Default: from POSTHOG_HOST, a cloud ingestion host turned into its app (https://eu.i.posthog.com becomes https://eu.posthog.com) and another host (a self-hosted PostHog) as it is; without POSTHOG_HOST, https://us.posthog.com. Set it when POSTHOG_HOST is a proxy. |
Where connection tokens live. The sign-ins made through Composio (Search Console, Google Analytics, Merchant Center, Webflow and Slack) are kept by Composio Cloud, under your own Composio account: your database stores only which Composio account each connection uses, never its OAuth or API tokens, and every call to those services goes through Composio. If your install must not send tokens to a third party, leave the
COMPOSIO_* variables unset: those cards then say “not configured” and the rest of Ansvisor works. The sign-ins Ansvisor keeps itself (stores, WordPress, Framer and Wix) are encrypted in your database with CONNECTOR_CREDENTIALS_KEY.Tips for production
- Mark secrets as sensitive — on Vercel, toggle the Sensitive flag on every secret env var. Once set, the value is write-only and can’t be read back from the UI.
- Rotate
SUPABASE_SERVICE_ROLE_KEYandCRON_SECRETquarterly as a hygiene practice - Use a different Supabase project for each environment (dev / staging / prod). Don’t share databases across environments — RLS bypasses make it easy to corrupt prod data with a misconfigured dev pointer
Continue: Upgrading
How to upgrade your self-hosted instance to a new release.