# Authentication Source: https://docs.ansvisor.com/api-reference/authentication How to authenticate API requests to the Ansvisor server. ## API keys (Public Metrics API) The versioned `/api/v1/*` endpoints on the **web app** authenticate with long-lived Ansvisor API keys (prefix `ans_`), created under **Settings → API Keys** in the dashboard: ```bash theme={null} curl "https://app.ansvisor.com/api/v1/brands" \ -H "Authorization: Bearer ans_..." ``` This is the right choice for external integrations — Looker Studio, Sheets, scripts, and the [MCP server](/guides/mcp-server). See the [Metrics API reference](/api-reference/metrics) for the endpoint list. ## Bearer token All `/api/*` endpoints on the Ansvisor server require a Supabase JWT bearer token in the `Authorization` header: ```bash theme={null} curl https://api.your-domain.com/api/health \ -H "Authorization: Bearer " ``` ## Getting a token The cleanest way is via the Supabase JS client in your own backend code: ```ts theme={null} import { createClient } from "@supabase/supabase-js"; const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY); const { data: { session }, } = await supabase.auth.signInWithPassword({ email, password, }); const token = session.access_token; ``` The web app does this automatically — every server action and fetch attaches the current session token transparently. ## Cron endpoints A handful of internal endpoints under `/api/internal/*` (used by Vercel Cron and the Stripe success callback) authenticate via a shared `CRON_SECRET` instead of user tokens: ```bash theme={null} curl -X POST https://api.your-domain.com/api/internal/daily-tracking \ -H "Authorization: Bearer $CRON_SECRET" ``` The same secret must be configured on both sides (Vercel env + server env). ## Public endpoints Three endpoints don't require any auth: * `GET /t.js` — tracking pixel script * `POST /track/{trackingCode}` — tracking pixel beacon (the tracking code identifies the brand; rate-limited) * `GET /` — basic uptime check, returns `{ "status": "ok", "service": "aeo-server", "mode": "cloud" | "self-hosted", "timestamp": "..." }` The `/api/health` endpoint IS auth-protected — by design, it returns the authenticated user's ID. For uptime monitoring, use `GET /` instead. CRUD endpoints for managing brands programmatically. # Brands Source: https://docs.ansvisor.com/api-reference/brands List, create, and manage brands programmatically. Most brand CRUD lives in the **web** app's server actions, not the standalone Express server. The endpoints below describe the public-facing patterns; the underlying tables live in Supabase and are accessible via the Supabase REST/RPC API directly with appropriate RLS-protected keys. ## List brands ```http theme={null} GET /rest/v1/brands?select=*,brand_domains(*),competitors(*) Authorization: Bearer apikey: ``` Returns brands the authenticated user's organization can see (RLS-filtered). ## Create a brand ```http theme={null} POST /rest/v1/brands Content-Type: application/json Authorization: Bearer apikey: { "organization_id": "uuid", "name": "Acme Corp", "slug": "acme-corp", "industry": "SaaS", "region": "US", "language": "en" } ``` After creation, you'll typically follow up with: * `POST /rest/v1/brand_domains` — register the primary domain * `POST /rest/v1/competitors` — add competitor records * `POST /rest/v1/prompt_sets` + `POST /rest/v1/prompts` — set up the tracked prompt list ## Update a brand ```http theme={null} PATCH /rest/v1/brands?id=eq. Content-Type: application/json Authorization: Bearer { "industry": "B2B SaaS" } ``` ## Delete a brand ```http theme={null} DELETE /rest/v1/brands?id=eq. Authorization: Bearer ``` Cascade-deletes all related rows: prompts, results, citations, suggestions, traffic logs. ## Schema reference See [Supabase setup → schema](/self-host/supabase-setup) for the full table definitions and relationships. Trigger and inspect AI tracking jobs. # Metrics API Source: https://docs.ansvisor.com/api-reference/metrics Pull AI visibility metrics into Looker Studio, Sheets, or your own dashboards with an API key. The Public Metrics API is a read-only, versioned REST surface under `/api/v1/*` on the **web app** host. It exposes the same aggregates the dashboard renders — visibility KPIs, trends, share of voice, citations, AI traffic, prompt performance and shopping cards — so you can blend Ansvisor data with GA4 / Search Console in an external reporting tool. These endpoints live on the web app (Next.js), not the Express API server. If you self-host, the base URL is your web app's domain. ## Authentication Every request needs an Ansvisor API key in the `Authorization` header. Create one under **Settings → API Keys** in the dashboard — the plaintext key (prefix `ans_`) is shown exactly once at creation time. ```bash theme={null} curl "https://app.ansvisor.com/api/v1/brands" \ -H "Authorization: Bearer ans_..." ``` Keys are scoped to the user who created them and resolve to that user's organization: every endpoint only returns data for brands the organization owns. Revoking a key (Settings → API Keys) takes effect immediately. `GET /api/v1/whoami` echoes the resolved identity — useful as a connection test: ```json theme={null} { "userId": "…", "email": "you@agency.com", "organizationId": "…" } ``` ## Conventions * All endpoints are `GET` and return JSON. * `brand_id` is required on every metrics endpoint; list your brands via `GET /api/v1/brands` first. * `date_from` / `date_to` accept `YYYY-MM-DD` (the `date_to` day is included through 23:59:59 UTC). * Optional filters shared by most endpoints: `model` (comma-separated model slugs), `region`, `topic_id`. * Errors return `{ "error": "…" }` with `400` (bad params), `401` (missing/invalid key), `404` (brand not in your organization) or `500`. ## Endpoints ### List brands ```http theme={null} GET /api/v1/brands ``` Returns `{ "brands": [{ "id", "name", "slug", "industry", "region", "created_at" }] }` for the caller's organization. ### Visibility summary ```http theme={null} GET /api/v1/visibility-summary?brand_id=&date_from=2026-06-01&date_to=2026-06-30 ``` Overall KPIs for the window: result count, average visibility score, total mentions, total citations, plus the top 5 competitors by mentions. Supports `model`, `region`. ### Visibility trend ```http theme={null} GET /api/v1/visibility-trend?brand_id=&granularity=day ``` Time-bucketed visibility for charting — the endpoint to join against GA4 date series. `granularity` is `day` (default) or `week` (ISO Monday buckets). Supports `date_from`, `date_to`, `model`, `region`, `topic_id`. Returns: ```json theme={null} { "granularity": "day", "buckets": [ { "date": "2026-06-01", "result_count": 24, "ai_visibility_score": 21.4, "avg_visibility_score": 47, "total_mentions": 31, "total_citations": 12, "avg_competitor_score": 39 } ] } ``` `ai_visibility_score` is the dashboard's headline Visibility number (0-100): `0.6 × mention rate + 0.25 × citation rate + 0.15 × position factor` over the day's answers. It is only populated for `day` granularity. `avg_visibility_score` and `avg_competitor_score` are legacy intensity averages kept for compatibility — prefer `ai_visibility_score`. The same field also appears on `/api/v1/visibility-summary` (`totals.aiVisibilityScore`, with `mentionAnswers` / `citationAnswers` / `positionFactor` components) and `/api/v1/competitor-comparison` (`ai_visibility_score` on the brand row and every competitor row). ### Competitor comparison & share of voice ```http theme={null} GET /api/v1/competitor-comparison?brand_id=&date_from=2026-06-01&date_to=2026-06-30 ``` The brand's benchmark row, one row per competitor (visibility, mentions, citations, appearances) and a `share_of_voice` block with the overall SoV percentage and a per-platform split. Supports `model`, `region`, `topic_id`. ### Citations ```http theme={null} GET /api/v1/citations?brand_id=&source_filter=external&limit=50 ``` The domains and URLs AI engines cite around the brand: totals, a source-type breakdown (owned / competitor / editorial / forum / social / review / institutional), `top_domains` and `top_urls`. `source_filter` is one of `all` (default), `owned`, `competitor`, `external`; `limit` caps the top lists (default 50, max 200). Supports `date_from`, `date_to`, `model`, `region`, `topic_id`. ### AI traffic ```http theme={null} GET /api/v1/ai-traffic?brand_id=&date_from=2026-06-01&date_to=2026-06-30 ``` Visits referred by AI platforms, collected by the tracking pixel: `total_visits`, `platform_breakdown`, `top_pages` (top 10 paths) and `country_breakdown`. ### Prompt performance ```http theme={null} GET /api/v1/prompt-performance?brand_id=&sort_by=visibility&order=desc&limit=10 ``` Per-prompt aggregates for the window: average visibility, mentions, citations, appearance count and the average competitor score per prompt. `sort_by` is `visibility` (default), `mentions`, `citations` or `appearances`; `order` is `desc` (default) or `asc`; `limit` 1–100 (default 10). Supports `date_from`, `date_to`, `model`, `region`, `topic_id`. ### Shopping cards ```http theme={null} GET /api/v1/shopping-cards?brand_id=&role=own&limit=50 ``` Normalized shopping-card rows (product title, brand, price, merchant, rating, position) captured from AI shopping surfaces. `role` filters to `own`, `competitor` or `other` matches; cursor-paginated via `limit` (default 50, max 200) and the returned `next_cursor`. ## Using it from Looker Studio Until a native Community Connector ships, any generic JSON/API connector (or a small Apps Script that calls these endpoints with `UrlFetchApp`) works: point it at `/api/v1/visibility-trend` with your key in the `Authorization` header and join on the `date` column. The endpoints are also what a future official connector will consume. The same API keys power the Ansvisor MCP server — query these metrics from Claude or any MCP client. # Tracking Source: https://docs.ansvisor.com/api-reference/tracking Trigger AI engine queries and inspect tracking jobs. ## Trigger a tracking run ```http theme={null} POST /api/tracking/check Content-Type: application/json Authorization: Bearer { "brandId": "uuid", "promptId": "uuid" } ``` `promptId` is optional — omit it to run every active prompt for the brand. The platforms and models come from the brand's own configuration, not from the request. Enqueues a tracking job. Returns: ```json theme={null} { "success": true, "jobId": "job-uuid", "message": "Tracking job enqueued" } ``` Errors: `400` when `brandId` is missing, `404` when the brand doesn't exist, `403` when it belongs to another organization, `409` when the brand is paused. In cloud mode the request can also return `402` (no active subscription or trial) or `429` (daily on-demand limit or cooldown reached). ## Poll job status ```http theme={null} GET /api/tracking/job/{jobId} Authorization: Bearer ``` Returns: ```json theme={null} { "success": true, "status": "active", "progress": { "current": 12, "total": 30, "promptText": "best running shoes for beginners", "model": "gemini-2.5-flash", "platform": "gemini" }, "result": null, "failedReason": null } ``` `status` values: `waiting`, `active`, `completed`, `failed`, `cancelled`. `result` is only set once the job is `completed`, and `failedReason` only when it is `failed`. An unknown `jobId` returns `{ "success": true, "status": "not_found" }`. To cancel a running job, send `DELETE /api/tracking/job/{jobId}` with the same bearer token. ## Internal cron endpoint The web app's cron route (`/api/cron/daily-tracking`) calls the server's internal endpoint with a shared secret: ```http theme={null} POST /api/internal/daily-tracking Authorization: Bearer ``` This loops over every active (not paused) brand, skips organizations without an active subscription (cloud mode) or without the daily monitoring feature, skips brands with no active prompts, and enqueues a tracking job for the rest. Returns: ```json theme={null} { "success": true, "triggered": 4, "total": 5 } ``` Vercel Cron triggers `/api/cron/daily-tracking` on a schedule defined in `web/vercel.json`. Self-hosted servers run the same flow on their own built-in daily schedule. To run it manually for testing, hit the server endpoint directly with the cron secret. ## Latest results For UI rendering, prefer the optimized RPC: ```http theme={null} POST /rest/v1/rpc/get_latest_prompt_results Content-Type: application/json Authorization: Bearer { "p_brand_id": "uuid", "p_platform": "chatgpt" // optional } ``` Returns the most recent `prompt_results` row per `(prompt_id, platform)` — much cheaper than `SELECT … ORDER BY created_at DESC LIMIT 1` per prompt in app code. Outbound webhooks for content briefs and lifecycle events. # Webhooks Source: https://docs.ansvisor.com/api-reference/webhooks Outbound webhooks for content opportunities and the Daily Pulse. ## Configuration Each brand has one webhook, set up in **Content → Configure Webhook**: * **Webhook URL** — your endpoint. Ansvisor sends `POST` requests with a JSON body. * **Secret key** (optional) — sent back to you in a header on every request, so your endpoint can tell that the call came from Ansvisor. * **Active** — pause delivery without deleting the webhook. The webhook receives every event below. Check the `event` field to tell them apart. ## Events | Event | Sent when | | - | - | | `opportunity.sent` | A user sends a content opportunity to the workflow — from the opportunity page, or several at once from the opportunities list | | `daily_pulse.created` | A brand's Daily Pulse is generated (on cloud and self-host alike) | | `webhook.test` | **Test Webhook** is pressed in the webhook settings | ## Verifying requests When a secret key is set, every request carries it unchanged in a header: ``` Content-Type: application/json X-Webhook-Secret: ``` Compare it with the secret you configured and reject the request on a mismatch. Use a constant-time comparison: ```js theme={null} import crypto from 'crypto'; function isFromAnsvisor(req, secret) { const received = Buffer.from(req.headers['x-webhook-secret'] ?? ''); const expected = Buffer.from(secret); return received.length === expected.length && crypto.timingSafeEqual(received, expected); } ``` The secret travels in the request itself, so only use an `https://` endpoint. ## Event payloads Payloads are flat JSON objects with an `event` field at the top level. ### `opportunity.sent` ```json theme={null} { "event": "opportunity.sent", "opportunity": { "id": "uuid", "title": "Publish a comparison page for project management tools", "description": "Competitors are cited for this prompt and you are not.", "type": "owned", "impact": "high", "opportunity_score": 82.5 }, "prompt": { "text": "best project management tools for agencies", "category": "comparison" }, "data": { "promptText": "best project management tools for agencies", "visibilityScore": 12, "competitorGap": 45, "competitorsCited": ["Competitor A", "Competitor B"] }, "brief": { "suggestedTitle": "…", "contentType": "comparison-page", "targetWordCount": 2000, "outline": [{ "heading": "…", "keyPoints": ["…"] }], "targetKeywords": ["…"], "competitorInsights": "…", "callToAction": "…" }, "brand": { "name": "Acme", "domain": "acme.example", "industry": "Software" }, "callback_url": "https://api.example.com/api/content//status" } ``` * `prompt` is `null` when the opportunity is not tied to a prompt. * `data` is the evidence behind the opportunity; its fields depend on the opportunity type. * `brief` is `null` until a content brief has been generated for the opportunity. * `callback_url` points at `PATCH /api/content/{id}/status`, which takes `{ "status": "in_progress" }` (or `new`, `sent`, `done`, `dismissed`). The endpoint authenticates with a dashboard user's access token — see [Authentication](/api-reference/authentication#bearer-token) — not with the webhook secret. Once your endpoint responds, the opportunity is marked `sent` whatever status code you return, and that status code and the first 1,000 characters of your response body are stored on it. If the request times out or the connection fails, the opportunity keeps its previous status. ### `daily_pulse.created` ```json theme={null} { "event": "daily_pulse.created", "brand": { "id": "uuid", "name": "Acme" }, "pulse": { "date": "2026-07-30", "frequency": "daily", "kpis": { "visibilityRate": 42.5, "visiblePrompts": 17, "promptCount": 40, "weekRate": 44.1, "prevWeekRate": 45.3, "weekTrend": -1.2, "mentions": 31, "mentionsChange": 4, "citations": 12, "citationsChange": -2, "sentimentPct": 68, "sentimentChange": 3, "totalResults": 120 }, "highlights": [ { "type": "first_citation", "key": "first_citation:https://acme.example/guide", "url": "https://acme.example/guide", "label": "…", "promptText": "…" } ], "warnings": [ { "type": "competitor_surge", "key": "competitor_surge:", "competitorName": "…", "from": 20, "to": 31 } ], "degraded_platforms": [], "insights_url": "https://app.example.com/dashboard/insights" } } ``` `frequency` is the brand's pulse setting (`daily`, `weekly` or `notable`); no pulse is generated when it is `off`. See the [Daily Pulse guide](/guides/daily-pulse) for when a pulse is sent and what each highlight and warning means. Highlight types and their fields: | `type` | Fields | | - | - | | `first_citation` | `url`, `label`, `promptText` | | `prompt_gain` | `promptText`, `gain` | | `competitor_overtaken` | `competitorName`, `brandRate`, `competitorRate` | | `new_engine` | `platform` | Warning types and their fields: | `type` | Fields | | - | - | | `sharp_drop`, `visibility_slipping` | `from`, `to`, `drop` | | `competitor_surge` | `competitorName`, `from`, `to` | | `competitor_crossed` | `competitorName`, `competitorRate`, `brandRate` | | `lost_citations` | `promptText` | Every highlight and warning also has a `key` that identifies its subject. ### `webhook.test` ```json theme={null} { "event": "webhook.test", "message": "This is a test webhook from AEO platform.", "timestamp": "2026-07-30T10:00:00.000Z" } ``` The test uses the URL and secret currently in the form, so you can check an endpoint before saving it. ## Delivery * Each event is sent **once**. There are no automatic retries. * Ansvisor waits up to 15 seconds for a response (10 seconds for `webhook.test`). * Respond with a 2xx status quickly and do slow work after responding. Common questions and troubleshooting. # Topics and Prompts Source: https://docs.ansvisor.com/concepts/topics-and-prompts The two primitives that drive every Ansvisor measurement. ## Prompt = a single tracked query A **prompt** is one natural-language question your potential customer might ask an AI engine. Examples: * *"best CRM for small business"* * *"how to automate email marketing for ecommerce"* * *"ChatGPT alternatives for enterprise"* * *"longest range luxury EV available in the US"* Each prompt runs on a schedule across the AI engines you've configured (ChatGPT, Gemini, Perplexity, Claude, Copilot, Grok). Every run produces a stored response with mention count, citations, sentiment, competitor mentions, and a visibility score for that prompt. ## Topic = a cluster of related prompts A **topic** is a label you (or the AI suggestion engine) attach to one or more prompts. Topics group prompts by intent, theme, or product area: | Topic | Example prompts | | - | - | | **Reliability** | *most reliable luxury sedan according to Consumer Reports*, *which luxury cars have lowest 10-year ownership cost* | | **EV Range** | *longest range luxury EV available in the US*, *best electric SUV with 300+ mile range* | | **Pricing** | *cheapest luxury brand to maintain long term*, *best executive sedans under \$100k* | Topics serve three purposes: The Topics page shows visibility per topic, so you spot which themes your brand owns and which it's losing. Content briefs are generated per topic, ensuring your editorial output covers your weakest themes first. When Ansvisor suggests new prompts to track, each suggestion comes with a topic label so you can see at a glance whether it expands an existing theme or opens a new one. ## How prompts get into the system Three sources: 1. **Onboarding suggestions** — when you create a brand, Ansvisor's AI generates 10 starter prompts based on industry and description. 2. **Prompt Suggestions** in the Insights tab — periodically, Ansvisor analyzes your tracked prompts + competitor citations + topic gaps and suggests new prompts to add. Each comes with a one-click `+` button. 3. **Manual entry** — you can always add a prompt directly with custom text and platform/region selection. ## How topics get created Two paths: * **Auto-created from suggestions** — when a new prompt suggestion comes with a topic label that doesn't exist for your brand yet, accepting the prompt creates the topic too. * **Manual** — you can rename, merge, or create topics on the Topics page. A prompt can belong to one topic at a time. ## Practical workflow A typical AEO operator's loop on Ansvisor: Check overall visibility delta. Drill into topics that dropped most. Find which prompts dragged the topic down — usually 1–2 prompts where a competitor surged. Click "Generate Brief" on those opportunities; receive structured brief (title, outline, keywords, competitor angles). Use the webhook integration to push the brief directly into Notion, Linear, Asana, or your editorial system. Pick up 1–3 fresh prompts each week from the Suggestions card to expand your tracked surface. Walk through the brand onboarding step by step. # Visibility scoring Source: https://docs.ansvisor.com/concepts/visibility-scoring How Ansvisor computes a 0–100 visibility score per brand, prompt, and platform. ## The score in one sentence Visibility score is the percentage of tracked AI responses that mention your brand by name, weighted by mention prominence and citation strength. A score of **0** means your brand never appears. **100** means it's mentioned in every response, prominently, with at least one citation. Most brands sit between 15–60% in healthy categories, so don't be alarmed if you start low. ## How a single response is scored Each time a prompt runs against an AI engine (e.g. "best CRM for small business" against ChatGPT), Ansvisor parses the response and computes a per-response score from three components: How many times your brand is named in the response. Capped at 5 to prevent spammy responses from skewing the score. Where in the response your brand appears. First mention in the answer counts more than a brand listed eighth in a long alternatives section. Whether the response includes a citation URL pointing to a domain you own or a third-party source that mentions you alongside the citation. The result is a 0–100 number per `(prompt, platform, run)` triple. ## Aggregation Dashboard visibility scores aggregate across multiple dimensions: * **Per prompt**: average score across all platforms over the last 7 days * **Per platform**: average across all prompts on that platform over the last 7 days * **Brand-level**: weighted average across all prompts and platforms in the active set Trend deltas (the green/red `+12%` you see on cards) compare the most recent 7-day window against the previous 7 days, on identical prompt sets, so the number is apples-to-apples. ## What moves your score | Action | Effect | | - | - | | Publish a citation-friendly comparison page | + over 1–4 weeks as LLMs re-index | | Earn coverage on a major review/listicle site | + within 2–7 days, often immediately | | Lose a press citation (article retracted) | – within a week | | A competitor outpaces you with better structured data | – relative score declines | | Add new prompts to your tracked set | Score may dip until those prompts mature | ## What does NOT move your score * Pure PageRank / backlinks without on-page content * Frequency of social posts (LLMs heavily underweight social vs. published content) * Time spent on traditional SEO without addressing AEO content patterns **Healthy growth target**: a 5–10% relative improvement per month is realistic for a focused AEO program in a competitive category. Anything faster usually requires either a major press placement or a structural content investment (e.g. publishing 30+ comparison pages in a quarter). ## Where to see it * **Insights**: brand-level score with sparkline + delta * **Topics**: average score per topic cluster * **Prompts**: per-prompt score with platform breakdown * **Brands page**: card-level score for cross-brand comparison How prompts cluster into topics and why that matters. # What is AEO? Source: https://docs.ansvisor.com/concepts/what-is-aeo Answer Engine Optimization — the discipline of being cited by AI search engines. ## SEO is for the 10 blue links. AEO is for the answer. For 25 years, organic discovery has meant ranking high on a Google results page. The strategy is well-known: keywords, backlinks, page speed, schema markup. The user clicks a result, lands on your site, you measure the visit. That funnel is shrinking. Tools like ChatGPT, Gemini, Perplexity, Claude, and Copilot give users a **direct answer** with a small handful of citations — and increasingly the user never clicks at all. ChatGPT alone serves over 200 million queries a day. Each one of those queries is a chance for an AI assistant to recommend your brand or recommend a competitor. **Answer Engine Optimization (AEO)** is the practice of making sure your brand is the one the AI cites. ## How AEO differs from SEO | | SEO | AEO | | - | - | - | | **Target surface** | Google SERP | AI assistant answers | | **Unit of optimization** | Page rank | Brand mention frequency | | **Signal source** | Crawled HTML, backlinks | LLM training data + retrieval | | **Measurement** | Position, impressions, CTR | Mention rate, citation share, answer presence | | **Time to impact** | Weeks to months | Daily/weekly (LLMs re-index quickly) | | **Killer asset** | Long-form content + links | Structured, citation-friendly content + topical authority | SEO and AEO are complementary, not competitive — both feed off authoritative content. But the *measurement system* is fundamentally different. You can't open Google Search Console and ask "how often did ChatGPT cite us last week?" That's the gap Ansvisor fills. ## What AEO looks like in practice Three things move the needle in AI engine answers: AI engines surface brands that have **broad, deep content** on a topic. A single great post is rarely enough; consistent coverage of the topical cluster is. Bullet lists, comparison tables, structured FAQs, schema markup, named-entity-rich text. Content that's easy for an LLM to extract verbatim is content that gets cited. Press, reviews, listicles, Reddit/Quora threads. AI engines weight third-party sources heavily because they reduce hallucination risk. Earned media is now an SEO+AEO double-win. ## What Ansvisor measures Every Ansvisor brand starts with a list of **prompts** — natural-language questions your potential customers ask AI engines (e.g. *"best CRM for small business"*, *"alternatives to Salesforce"*). Multiple times per week, Ansvisor: 1. Asks each prompt to every configured AI engine 2. Parses the response for brand mentions, citation URLs, and competitor mentions 3. Computes a **visibility score** per brand, per prompt, per platform 4. Surfaces gaps: prompts where competitors are cited but you aren't 5. Generates content briefs targeting those gaps The output is a continuously-updated AEO instrumentation panel. Same way Google Analytics changed how marketers measure SEO, Ansvisor is built to be the AEO equivalent. Learn the math behind your visibility score and what changes it. # FAQ Source: https://docs.ansvisor.com/faq Common questions about Ansvisor. Yes. Every line of code that powers ansvisor.com is in the [public repo](https://github.com/ansvisor/ansvisor) under MIT. No paywalled features in the self-hosted build — the cloud service is just a hosted convenience. Cloud has a flat monthly subscription that includes everything (AI provider quotas, infrastructure, support). Self-hosted is free in software cost but you pay your own AI provider bills (OpenAI, Gemini, Anthropic) plus DataForSEO/Cloro if you use them. Rough back-of-envelope: a brand tracking 30 prompts daily across 4 AI engines costs \~\$5–15/month in API fees, depending on response length and provider mix. For programmable AI engines (OpenAI, Anthropic, Google Gemini API), Ansvisor uses official APIs — most reliable, well-priced, and TOS-compliant. For *web* AI engines that don't have public APIs (like ChatGPT.com search results), Ansvisor uses Cloro as a managed scraping backend. Cloro handles browser fingerprinting, captcha bypass, and rate-limiting at scale. These are two distinct fetchers: **Cloro** scrapes the AI answer engines, while **Scrape.do** (via `SCRAPEDO_API_KEY`) fetches your *own* pages for the **Site Audit** feature (proxy + JS render). Self-hosters who want Site Audit need a Scrape.do key. Default schedule is once per day per brand. You can configure per-platform frequency under brand settings (e.g. ChatGPT daily, Perplexity 3x/week, Gemini weekly) to manage AI provider costs. Probably not. Brand-new brands or those in non-English-language niches commonly start at 0 across most prompts. The score reflects what AI engines *currently* cite — closing that gap with content + earned media is exactly what AEO is for. Yes. Click any prompt or platform cell on the Insights page; the drill-down sheet shows the full LLM response, parsed citations, sentiment, and detected competitor mentions. * **Bugs / feature requests**: [GitHub Issues](https://github.com/ansvisor/ansvisor/issues) * **Discussion**: [GitHub Discussions](https://github.com/ansvisor/ansvisor/discussions) * **Cloud customers**: support email in your dashboard footer For self-hosted: 100% on your infrastructure, never touches Ansvisor servers. For cloud: standard SaaS privacy — your prompts and responses are stored in our managed Supabase, isolated per organization via RLS, never used for training. Full DPA available on request for enterprise plans. Yes — 13 languages and 18 regions supported. AI engines respond in the language of the prompt; Ansvisor's mention/citation parsing is language-agnostic. Read [CONTRIBUTING.md](https://github.com/ansvisor/ansvisor/blob/main/CONTRIBUTING.md) in the repo. We welcome PRs for: new AI engine adapters, additional citation classifiers, language packs, UI polish, and bug fixes. For larger features, open an issue first to align. # Add your first brand Source: https://docs.ansvisor.com/guides/add-your-first-brand Step-by-step walkthrough of brand onboarding. ## Prerequisites * A logged-in Ansvisor account ([cloud](https://app.ansvisor.com/sign-up) or [self-hosted](/self-host/docker-compose)) * 5–10 minutes * The website URL of the brand you want to track ## Onboarding flow The onboarding wizard has **5 steps for self-hosted** instances and **6 steps for the cloud** (the extra step is plan checkout). Each step auto-saves so you can resume later if you drop off. Three fields: * **Brand name** — exactly how the brand is named in the wild (e.g. *Mercedes-Benz*, not *MBZ*). Casing matters — suggestions copy your spelling, mention detection is case-insensitive. * **Website** — your main domain without `https://` prefix (e.g. `mbusa.com`, `linear.app`). Used for citation classification, AI traffic attribution, and competitor research. * **Describe your brand** *(optional but recommended)* — 1–3 sentences covering what you do and who your audience is. Fed into AI to generate better topics, prompts, and competitor suggestions. The industry isn't asked as a separate field — Ansvisor infers it from your description. Pick the primary market and language for tracking. This drives: * Which language AI engines respond in (English prompts return English answers, etc.) * Region weighting on search engines that have it (Google AI Overview / Mode geo-localize) * DataForSEO query routing for prompt volume estimation 18 regions and 13 languages supported. You can change these later per-prompt if you have multi-region needs. Ansvisor uses your brand description and an AI model to suggest 6–10 **topics** — themes your customers care about (e.g. *EV Range*, *Reliability*, *Pricing*). Review the list and check the topics that fit your brand. Uncheck any that don't apply. You can also type a custom topic name and add it manually. Aim for 4–7 topics. Too few = narrow tracking surface. Too many = noisy and hard to interpret. For each selected topic, Ansvisor generates **5 starter prompts** (real questions a potential customer would ask an AI engine). Review the full list: * Edit prompt text inline if a phrasing feels off * Delete prompts that don't fit * Add custom prompts at the bottom These prompts will run on every AI platform allowed by your plan. Self-hosted unlocks all 8 (ChatGPT, Google AI Overview, Google AI Mode, Google Gemini, Perplexity, Microsoft Copilot, Grok, Claude). Starter plan runs ChatGPT + Perplexity by default; Growth and Enterprise unlock the rest. Avoid prompts that include your brand name (e.g. *"What is Acme?"*). They almost always score 100 because the AI obviously talks about you when asked — but they don't measure organic AEO performance. Keep your tracked prompts brand-agnostic. Ansvisor analyzes your brand and suggests 3–8 competitor names. Review the list: * Check the ones that are actual rivals * Uncheck ones that aren't really comparable * Add custom competitors at the bottom (use the name as it appears in AI responses, e.g. *Salesforce*, not *salesforce.com*) Competitor tracking is unlocked on Growth and Enterprise plans on the cloud, and always available on self-hosted. On the cloud, the final step picks your subscription plan (Starter, Growth, or Enterprise) and runs Stripe Checkout. All paid plans include a **14-day free trial** — you enter card details but no charge happens until day 15. Self-hosted instances skip this step entirely. ## What happens after you finish The first round of AI engine queries kicks off automatically and finishes in 5–15 minutes (depending on prompt count and plan). The dashboard updates live as platforms report back. While you wait: * **Insights** — first KPI cards appear within 1–2 minutes of the run starting * **Citations** — populates as AI responses come back with source URLs * **Prompts → Insights tab** — Suggestions card refreshes after the run, surfacing new prompts to add ## Adding the tracking pixel (optional, post-onboarding) The onboarding flow doesn't include the tracking pixel — it's an opt-in step you do separately for **AI Traffic Analytics** (real visits arriving from AI answer engines). To set it up: Navigate to `/dashboard/traffic` after onboarding. The snippet is shown at the top of the page. Looks something like: `` Drop the snippet just before the closing `` tag. Next.js: add it inside `app/layout.tsx`. WordPress: use a header injection plugin. Static sites: edit your template. Visits start logging immediately; the dashboard updates near-realtime. ## Common pitfalls Probably not. New, niche, or B2B brands often start at 0 across most prompts. AI engines need either (1) significant training data mentioning your brand, or (2) live citations from authoritative sources. The score only stays at 0 when both are missing — which is exactly what AEO is for. Yes. *"What is Linear?"* style prompts almost always score 100 (the AI obviously talks about you when asked) but they're not measuring organic discovery. Remove brand-name prompts; they distort averages. That's gold. Those are AI-suggested alternatives users see when you're not cited. Add the most-mentioned ones to your competitors list to track them properly. Starter plan runs only ChatGPT and Perplexity. Google AI Overview, Google AI Mode, Gemini, Copilot, Grok, and Claude unlock on Growth and Enterprise plans (cloud), or are all available immediately on self-hosted. Yes. From the **Topics** page or the **Suggestions** card in **Prompts → Insights**, you can add new topics and prompts at any time. Tracking immediately picks them up on the next scheduled run. Now that data is flowing, learn to read the dashboard. # AI Traffic Analytics Source: https://docs.ansvisor.com/guides/ai-traffic-analytics Measure real visits to your site from AI answer engines. ## Why this exists Most analytics platforms (Google Analytics, Plausible, etc.) lump AI traffic into "direct" or "referral" buckets — they can't tell you a visitor arrived from ChatGPT vs. perplexity.ai. Ansvisor's tracking pixel reads the referrer chain, classifies AI sources by domain, and gives you a clean breakdown. ## Setup On the **AI Traffic Analytics** page (`/dashboard/traffic`), copy the snippet at the top. It looks like: ```html theme={null} ``` Add the script before `` on every page you want to track. For Next.js, drop it in your root layout. For Wordpress, use a header injection plugin. For static sites, edit the template. Visits start appearing immediately. The dashboard updates in near-realtime (≤1 minute). ## What you see Total AI-referred visits, top platform, unique platforms — all with 7-day delta. Daily breakdown by AI platform, last 7 days. Per-platform visit counts with week-over-week change. Which URLs on your site receive the most AI-driven traffic. Real-time stream of the last 10–50 AI visits with platform, page, country, and timestamp. ## Supported AI sources Pixel detects and classifies referrers from: * ChatGPT (chatgpt.com, chat.openai.com, search.chatgpt.com) * Perplexity (perplexity.ai) * Claude (claude.ai) * Gemini (gemini.google.com) * Copilot (copilot.microsoft.com) * You.com, Phind, Meta AI, Poe Anything else falls into `unknown` — likely a future AI source we haven't catalogued yet. The list is updated regularly. ## Privacy The pixel collects: referrer, URL, country (via IP geolocation), language, screen size, anonymized IP, user agent. **It does NOT use cookies, fingerprinting, or any cross-site identifiers.** No GDPR consent banner needed in most jurisdictions. ## Common patterns * **Spike on a Friday** — usually a viral Reddit/Twitter thread that AI engines indexed and started recommending * **Sudden country shift** — Perplexity or Gemini changed regional weighting; not a metric you control * **Specific page surge** — one of your articles is being cited as authoritative; lock it in with internal links to defend Benchmark your AI presence against named rivals. # Answer Engine Insights Source: https://docs.ansvisor.com/guides/answer-engine-insights The Insights page is your daily AEO command center. ## What you see The **Insights** page (`/dashboard/insights`) is the main analytics hub for a brand. It breaks down your AI visibility across four panels: Brand-level visibility score, mention count, citation count, and active prompt count — each with a 7-day delta against the prior week. A 30-day line chart of visibility per platform. Spot platform-specific dips (e.g. Perplexity dropped while ChatGPT held) and correlate with content publication or competitor activity. Stacked bars showing which AI engines drive the most/fewest mentions for your brand. Useful for routing content investment toward your weakest platform. Click any prompt or platform cell to open a side sheet with the actual AI responses, citations, and competitor mentions for that cell. ## Reading the visibility number A few quick interpretations to anchor you: * **Under 15%** — Cold start. Brand barely known to AI engines on these prompts. * **15–35%** — Emerging presence. AI sometimes cites you but competitors dominate. * **35–60%** — Established. You're reliably cited; focus on the 1–2 platforms or topics still trailing. * **Over 60%** — Category leader. Your job is now defending the moat against new entrants. The exact threshold depends heavily on category competitiveness. *"longest range luxury EV"* might have a clear winner at 40%, while *"best CRM for small business"* is so saturated that 20% is leadership. **Don't fixate on the absolute number — fixate on the delta.** A brand growing from 18% → 24% in a quarter is healthier than a stagnant 50%. ## Common workflows ### Weekly review (10 min) Up or down? If down ≥10%, dig into the trend chart to find when the drop happened. Pick the platform contributing least; that's your next investment. Look at AI-suggested new prompts and topics gap is widening; queue 1-3 to add. From Content Optimization, generate a brief for the worst-performing prompt this week. ### Investigation flow (30 min) When a metric drops dramatically: Side sheet shows the actual AI response. Read it: is your brand still mentioned but ranked lower? Or completely absent? The drill-down lists every competitor cited in that response. New competitor surge is often the cause. Compare citation URLs from this run vs. last week. A retracted article or a new competitor blog post is often the trigger. Either: ship a content piece targeting the prompt (Content Optimization), or earn a placement (PR), or accept it as a one-time blip. ## Filters and date range Top-right of the page: * **Platform** — restrict to a single AI engine * **Region** — filter by user region (if your prompts run in multiple) * **Topic** — narrow to one topic cluster * **Date range** — 7d / 30d / 90d / custom Filters apply to all four panels simultaneously. Drill into topic clusters to find which themes you own and which you're losing. # Citations Source: https://docs.ansvisor.com/guides/citations Every URL AI engines cite alongside your brand, classified by source type. ## What it shows The **Citations** page (`/dashboard/citations`) aggregates every URL that AI engines cited across your tracked prompts. Each row shows: * The URL and its domain * **Source type** classification (news, review, owned, social, forum, listicle, vendor) * Number of times it was cited in the last 30 days * Which prompts triggered it * Whether your brand was mentioned in that citation's content ## Source classification Ansvisor automatically classifies citations into seven categories: | Type | Examples | Why it matters | | - | - | - | | **Owned** | your-domain.com, blog.your-domain.com | Pages you control — the easiest to optimize | | **News** | TechCrunch, The Verge, Bloomberg | High-trust signal; one good placement = months of AEO impact | | **Review** | G2, Capterra, TrustPilot, Wirecutter | AI engines weight these heavily for buying-intent prompts | | **Listicle** | "Best 10 X" articles | Easy citation pickups via outreach | | **Forum** | Reddit, Hacker News, Quora | Surprisingly influential for technical/B2B queries | | **Social** | Twitter, LinkedIn | Lowest weight in current LLMs | | **Vendor** | direct competitor sites | Indicates you're losing comparison ground | ## How to use it ### Find your earned-media wins Sort by `times cited` descending → filter by `source type: news, review, listicle`. Anything in the top 20 is doing meaningful AEO work for you. ### Find your defensive gaps Filter by `source type: vendor` → these are competitor pages that AI engines cite when answering questions in your category. Each one is an opportunity for a counter-piece. ### Identify outreach targets Filter by `source type: review, listicle` → look for high-traffic articles where your brand is **not** mentioned. Those are direct outreach targets — ask for inclusion. ## Drill-down Click any citation row to see: * Which exact AI responses cited it * The prompt that triggered each citation * Whether your brand was named in the cited page's content Measure real visits arriving on your site from AI answer engines. # Competitors Source: https://docs.ansvisor.com/guides/competitors Track named competitors and benchmark visibility side-by-side. ## Adding competitors On the **Competitors** page (`/dashboard/competitors`) click `+ Add competitor`. Each competitor has: * **Name** — exactly as it appears in AI responses (e.g. *Salesforce*, not *salesforce.com*) * **Domain** (optional) — used for citation cross-checks Aim for 3–8 competitors. Fewer than 3 and benchmarks have no signal; more than 8 and the comparison view gets noisy. ## What the page shows For each competitor: * Mention frequency across your tracked prompts (last 7 days, last 30 days) * Visibility share vs. yours * Top 3 prompts where the competitor leads * Trend delta — is the competitor gaining ground or losing it? ## Comparison views Drill into any prompt: shows your visibility vs. each competitor on that single prompt across all platforms. Each topic shows which competitor leads, the gap percentage, and how the gap has moved week-over-week. Some competitors might dominate Perplexity but underperform on ChatGPT (or vice-versa). The platform skew chart highlights these for targeted optimization. ## Auto-discovered competitors Even if you don't add competitors manually, AI responses **mention** other brands when answering your prompts. The Insights drill-down sheet exposes these — recurring names you didn't add are strong candidates for the competitors list. **Workflow tip**: monthly, review the auto-discovered names. If a brand you've never heard of is being cited 3+ times across your prompts, add it to your list. They're a stealth competitor that AI engines already know about. ## Closing visibility gaps Once a gap is visible, the workflow is: 1. Identify the prompt(s) where the competitor leads most 2. Inspect the AI response — what content/citation is the competitor winning with? 3. Generate a content brief targeting that prompt (Content Optimization) 4. Ship the piece, earn placements, monitor 2–4 weeks for the gap to close Generate AI content briefs to systematically close visibility gaps. # Content Optimization Source: https://docs.ansvisor.com/guides/content-optimization Turn visibility gaps into editorial briefs delivered to your CMS. ## What this module does The **Content Optimization** page (`/dashboard/content`) lists *opportunities* — prompts where you have a visibility gap relative to competitors. Each opportunity carries: * A title (the angle of attack) * An impact rating (high / medium / low) based on prompt volume × visibility gap * Source data (related prompt, est. AI volume, visibility, competitor gap %, intent, keywords, competitors cited) * An "opportunity score" 0–100 ## From opportunity to brief Navigate from the list to the detail page. You see the full source data and any existing brief. If no brief exists, click **Generate Content Brief**. An LLM produces a structured brief with: * Suggested article title * Content type (technical preview, comparison, how-to, listicle, etc.) * Target word count * Outline (5–8 sections, each with key points) * Target keywords (5–8 SEO + AEO blended) * Competitor insights paragraph (what rivals are doing right, where the gap is, what to attack) * Call to action recommendation Click **Send to workflow** to fire a webhook with the brief payload. The opportunity status moves from `new` → `sent`. Once your team ships the article, mark the opportunity as `done`. Ansvisor monitors the related prompt; if visibility climbs ≥10% in the following 4 weeks, you'll get a "win recap" notification. ## Webhook configuration Set up a webhook once, fire briefs at it from any opportunity: * Open `/dashboard/content` and click **Webhook settings** in the toolbar * Set the **URL** (e.g. Notion automation, Zapier, Linear webhook, custom endpoint) * Set a **secret** (HMAC-signed payloads — recommended) * Toggle **Active** Payload schema: ```json theme={null} { "opportunityId": "uuid", "brandId": "uuid", "brandName": "string", "opportunity": { ...full opportunity object... }, "brief": { ...full brief object... }, "sentAt": "2026-05-06T10:00:00Z" } ``` ## Bulk operations On the listing page you can: * Multi-select opportunities and **bulk send to webhook** (great for kicking off a content sprint) * Bulk update status (mark a batch as `dismissed` if they're not relevant this quarter) ## Strategy notes * **Don't ship every brief** — pick the top 2–4 high-impact opportunities per week. Editorial bandwidth is the bottleneck. * **Re-generate briefs after publishing** — once you've shipped one piece, regenerate adjacent briefs; the AI now has different competitive context * **Mix owned and earned** — some opportunities are best closed with a guest post or PR placement (`type: earned`), not a new owned page Ready to run Ansvisor on your own infrastructure? # Daily Pulse Source: https://docs.ansvisor.com/guides/daily-pulse A short per-brand digest after each tracking run: KPIs, highlights and warnings, by email or webhook. The Daily Pulse is a short digest for each brand that goes out after its scheduled tracking run. It shows the headline numbers for the period, then lists what went well (**highlights**) and what needs attention (**warnings**). It is delivered two ways: * **Email** — cloud only. * **The `daily_pulse.created` webhook** — on cloud and self-hosted alike. See [Webhook delivery](#webhook-delivery). ## Configure it Open **Settings → Notifications**. Every active brand in your organization has its own row with a frequency and a recipient list. Admins and managers can change them; other members see the settings read-only. The **Manage notification settings** link at the bottom of every pulse email opens this tab. ### Frequency | Setting | What happens | | - | - | | **Daily** (default) | A pulse after every scheduled daily tracking run. | | **Weekly** | One pulse a week, on **Monday (UTC)**, covering the last 7 days compared with the 7 days before. | | **Only when notable** | Same timing as Daily, but the pulse is skipped when it has no highlights and no warnings. | | **Off** | No pulse at all — no email and no webhook. | A brand with no saved setting uses **Daily**. "Notable" means at least one highlight or one warning survived the rules below — including the 7-day warning cooldown. A day whose only warnings were already reported that week counts as not notable. ### Recipients Enter a comma-separated list of email addresses. * **Leave it empty to send to every member of the organization.** * Each address is checked when you save. If any entry is not a valid email address, the save is rejected and the invalid entries are listed under the field — fix or remove them and save again. A mistyped address is never silently dropped, because an empty list would widen the audience to the whole organization. * Up to 50 addresses are stored. Recipients only affect email. Webhook delivery does not use them. ## When it is sent A pulse is generated after a brand's **scheduled** daily tracking run finishes. Manual runs and single-prompt refreshes never trigger one. Before computing anything, the pulse waits (up to 90 minutes) for the AI engines' late answers from that run to arrive, so its numbers match what the dashboard shows for the same window. No pulse is sent when: * the frequency is **Off**, or it is **Weekly** and today is not Monday (UTC) * the brand already had a pulse today, or a pulse for the same tracking run was already sent * there are no tracking results in the window * the frequency is **Only when notable** and there is nothing notable * on the cloud, the organization's subscription is not active or trialing If a scheduled run finished but its pulse was lost (for example, the server restarted at the wrong moment), it is sent at the start of the next daily cycle instead. ## What's in a pulse ### KPIs | KPI | Meaning | | - | - | | **Visibility** | Your [AI Visibility Score](/concepts/visibility-scoring) for the period | | **7-day trend** | The score over the last 7 days, and its change from the 7 days before | | **Mentions** / **Citations** | Totals for the period, and the change from the previous period | | **Positive sentiment** | Share of answers mentioning you that were positive, and its change | | **Prompts you appear in** | Prompts where you were visible, out of all tracked prompts | For **Daily** and **Only when notable**, the period is the latest completed tracking run (at most 24 hours), compared with the run before it. For **Weekly**, it is the last 7 days compared with the 7 days before. The email subject carries the score and the weekly trend, for example: `Acme Daily Pulse — visibility 42.5 (+1.2 pts this week)`. ### Highlights | Highlight | When it appears | | - | - | | **First citation** (`first_citation`) | A URL from a prompt's **Target URLs** list was cited by an AI engine for the first time during the period. Up to 5 are listed. | | **Prompt gain** (`prompt_gain`) | A prompt's visibility score rose by 5 points or more this week versus last week. The top 3 gains are listed. | | **Competitor overtaken** (`competitor_overtaken`) | Last week a competitor's visibility score was at or above yours; this week yours is higher. | | **New engine** (`new_engine`) | Your brand was mentioned or cited on an AI platform for the first time ever. | Highlights other than first citations and new engines compare the last 7 days with the 7 days before, whatever the frequency. ### Warnings | Warning | When it appears | | - | - | | **Sharp drop** (`sharp_drop`) | Your visibility score fell week-over-week by at least 3 points **and** at least 30% of last week's score. Only for brands with 10 or more tracked prompts. | | **Visibility slipping** (`visibility_slipping`) | A smaller fall: at least 3 points and at least 10% of last week's score, from a score of 10 or more. Same 10-prompt minimum. | | **Competitor surge** (`competitor_surge`) | A competitor's visibility score rose by 15 points or more week-over-week. | | **Competitor crossed** (`competitor_crossed`) | A competitor that was at or below your score last week is above it this week (and did not already trigger a surge). | | **Lost citations** (`lost_citations`) | A prompt with an estimated AI volume above zero was cited on at least 60% of its result days in the last 14 days, then not at all on its 3 most recent result days. | **Visibility slipping** is included in the webhook payload, but the email does not list it. **Cooldown.** The same warning about the same subject — for example, a surge by the same competitor — is not repeated within 7 days. Highlights have no cooldown. **Data-collection guard.** If an AI platform's result volume collapses on our side (under a quarter of its usual daily volume across the whole instance), the pulse treats it as a collection problem rather than a real change: **Sharp drop**, **Visibility slipping** and **Lost citations** are held back, and the email notes which platforms were affected. The webhook reports them in `degraded_platforms`. ## Webhook delivery Every generated pulse is also sent as a `daily_pulse.created` event to the brand's active webhook, configured under **Content → Configure Webhook**. This works on self-hosted instances too, where it is the only delivery channel. The webhook payload contains the same KPIs, highlights and warnings as the email, plus the pulse date, the frequency and a link to Insights. The frequency setting applies to the webhook as well: **Off** sends nothing, **Weekly** sends on Mondays only. The recipient list does not. See [Webhooks](/api-reference/webhooks#event-payloads) for the full payload, field reference and delivery rules. Dig into the numbers behind each pulse on the Insights dashboard. # Looker Studio Source: https://docs.ansvisor.com/guides/looker-studio Blend Ansvisor AI visibility metrics with GA4 and Search Console in one Looker Studio dashboard. The Ansvisor Community Connector pulls your AI visibility metrics into Looker Studio, so a single dashboard can show AI visibility next to organic traffic: GA4 sessions, Search Console clicks, and Ansvisor's visibility score on one date axis. The connector is distributed by deployment link while it awaits the partner gallery. Ask us for the current link, or deploy your own copy from [`integrations/looker-studio`](https://github.com/ansvisor/ansvisor/tree/main/integrations/looker-studio) in the repo. ## Connect 1. Create an API key in the dashboard under **Settings → API Keys** and copy the `ans_...` token — it's shown only once. 2. Open the connector's deployment link. Looker Studio asks you to authorize the connector, then prompts for a key: paste the token. 3. Pick the **brand** and a **metric set**, then hit **Connect** → **Add to report**. ## Metric sets Each data source exposes one metric set. Add several data sources if you need more than one in a report. | Metric set | Dimension | Metrics | | - | - | - | | **Visibility Trend (daily)** | Date | Avg visibility score, mentions, citations, tracked results, competitor avg | | **Share of Voice by Platform** | Platform | Brand mentions, competitor mentions, SoV % | | **Citations — Top Domains** | Domain, category | Citations, results citing, usage % | | **AI Traffic by Platform** | Platform | Visits | The report's date range control drives every request — no per-source date configuration needed. ## Blending with GA4 The Visibility Trend set is built for blending: join it to a GA4 source on the **Date** dimension and plot *Avg Visibility Score* against *Sessions* on a dual-axis time series. A rising visibility line that precedes a traffic lift is the clearest way to show AI-search impact to a client. ## Notes & limits * Responses are cached for 5 minutes per user, so dashboards with many charts don't hammer the API. * The key is scoped to its creator's organization: only that organization's brands appear in the brand picker. * Revoking the key in Settings immediately breaks the data source (it will ask for new credentials). The same endpoints, documented — for Sheets, Power BI or custom integrations. # MCP server Source: https://docs.ansvisor.com/guides/mcp-server Connect Claude, Codex, Cursor, VS Code, Gemini CLI, Hermes Agent, OpenClaw and any MCP-aware client to your Ansvisor data — zero install. Ansvisor ships a [Model Context Protocol](https://modelcontextprotocol.io) server at `https://app.ansvisor.com/api/mcp` (or your own host if you self-host) so AI assistants can query your brand visibility data through natural-language conversation. No npm package, no local process — just a URL and an API key. ## Generate an API key 1. Open your Ansvisor dashboard → **Settings** → **API Keys** 2. Click **New key**, give it a memorable name (e.g. *Claude — laptop*) 3. Copy the token. It's shown **once** — store it in your password manager The dialog that shows the key also has a ready-to-paste setup for each client below, with the key filled in. The snippets on this page read the key from an environment variable instead, so it stays out of your config files: ```bash theme={null} export ANSVISOR_TOKEN=ans_... ``` Every client connects to the same URL with the same header: `Authorization: Bearer `. There is no OAuth sign-in. ## Connect your client ### Claude Code ```bash theme={null} claude mcp add --transport http ansvisor https://app.ansvisor.com/api/mcp \ --header "Authorization: Bearer ${ANSVISOR_TOKEN}" ``` ### Claude Desktop Claude Desktop adds remote servers as **custom connectors**. The `claude_desktop_config.json` file only starts local servers, so a `url` entry there is ignored. 1. Open **Settings → Connectors** and choose **Add custom connector** 2. Name it *Ansvisor* and enter `https://app.ansvisor.com/api/mcp` as the URL 3. Under authentication choose **No sign in** 4. Under **Request headers**, add `Authorization` with the value `Bearer ans_...` On Team and Enterprise plans, an owner adds the connector under **Organization settings → Connectors** first. ### Codex CLI ```bash theme={null} codex mcp add ansvisor --url https://app.ansvisor.com/api/mcp \ --bearer-token-env-var ANSVISOR_TOKEN ``` Or add it to `~/.codex/config.toml`: ```toml theme={null} [mcp_servers.ansvisor] url = "https://app.ansvisor.com/api/mcp" bearer_token_env_var = "ANSVISOR_TOKEN" ``` ### Cursor Add to `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project): ```json theme={null} { "mcpServers": { "ansvisor": { "url": "https://app.ansvisor.com/api/mcp", "headers": { "Authorization": "Bearer ${env:ANSVISOR_TOKEN}" } } } } ``` ### VS Code Add to `.vscode/mcp.json`, or run **MCP: Open User Configuration** from the Command Palette to add it for every workspace. VS Code asks for the key the first time it connects: ```json theme={null} { "inputs": [ { "type": "promptString", "id": "ansvisor-token", "description": "Ansvisor API key", "password": true } ], "servers": { "ansvisor": { "type": "http", "url": "https://app.ansvisor.com/api/mcp", "headers": { "Authorization": "Bearer ${input:ansvisor-token}" } } } } ``` ### Windsurf In the Cascade panel, open **… → Open MCP config file** and add: ```json theme={null} { "mcpServers": { "ansvisor": { "serverUrl": "https://app.ansvisor.com/api/mcp", "headers": { "Authorization": "Bearer ${env:ANSVISOR_TOKEN}" } } } } ``` ### Gemini CLI ```bash theme={null} gemini mcp add --transport http \ --header "Authorization: Bearer ${ANSVISOR_TOKEN}" \ ansvisor https://app.ansvisor.com/api/mcp ``` This writes the server to `~/.gemini/settings.json` with `httpUrl`. Use `httpUrl`, not `url`, if you edit the file by hand: `url` means the older SSE transport. ### Hermes Agent Add to `~/.hermes/config.yaml`. Hermes reads `ANSVISOR_TOKEN` from your environment or from `~/.hermes/.env`: ```yaml theme={null} mcp_servers: ansvisor: url: "https://app.ansvisor.com/api/mcp" headers: Authorization: "Bearer ${ANSVISOR_TOKEN}" ``` ### OpenClaw Add to `~/.openclaw/openclaw.json`: ```json theme={null} { "mcp": { "servers": { "ansvisor": { "url": "https://app.ansvisor.com/api/mcp", "transport": "streamable-http", "headers": { "Authorization": "Bearer ${ANSVISOR_TOKEN}" } } } } } ``` Then check the connection with `openclaw mcp probe ansvisor`. ### Zed Add to your Zed `settings.json`. Zed does not expand environment variables here, so the key goes in directly: ```json theme={null} { "context_servers": { "ansvisor": { "url": "https://app.ansvisor.com/api/mcp", "headers": { "Authorization": "Bearer ans_..." } } } } ``` ### Other clients Any client that supports remote MCP servers over Streamable HTTP works with the URL and the `Authorization` header above. If yours can only start local (stdio) servers, bridge it with [`mcp-remote`](https://github.com/geelen/mcp-remote): ```json theme={null} { "mcpServers": { "ansvisor": { "command": "npx", "args": [ "-y", "mcp-remote", "https://app.ansvisor.com/api/mcp", "--header", "Authorization:${AUTH_HEADER}", "--transport", "http-only" ], "env": { "AUTH_HEADER": "Bearer ans_..." } } } } ``` Keep `Authorization:${AUTH_HEADER}` without a space, with the `Bearer ...` value in `env`: some clients do not escape spaces inside `args`. ## Check the connection Ask your client *"List my Ansvisor brands"*. It should call the `list_brands` tool and show your brands. If it reports a 401, the header is missing or the key was revoked. ## Self-host? Point at your own instance The MCP endpoint lives at `/api/mcp` on whatever host runs the Ansvisor web app. If you self-host at `ansvisor.mycompany.internal`, use that base URL instead. ## Available tools The MCP server exposes **29 tools**. Descriptions below match [`web/src/lib/mcp/server.ts`](https://github.com/ansvisor/ansvisor/blob/main/web/src/lib/mcp/server.ts). Tools that change data are marked **writes** — only call them on explicit user intent, not during exploratory queries. ### Brands | Tool | What it does | Writes | | - | - | - | | `list_brands` | List the brands the authenticated user can access (id, name, slug, industry, region). Call first to resolve a `brand_id`. | | ### Visibility & analytics | Tool | What it does | Writes | | - | - | - | | `get_visibility_summary` | Aggregate visibility metrics for a brand over an optional date range and filters — headline `aiVisibilityScore`, mentions, citations, competitors. | | | `get_visibility_trend` | Visibility over time (day or week buckets) with mentions, citations, and competitor scores for trend questions. | | | `get_competitor_comparison` | Competitor benchmark and share of voice for a window, comparable visibility rates and per-model/platform splits. | | | `list_citations` | URLs and domains AI engines cite alongside a brand, by source type, with top domains/URLs and usage stats. | | | `get_ai_traffic` | AI-referral traffic (visits by engine, landing pages, countries) for an optional date range. | | | `get_prompt_performance` | Per-prompt performance (visibility, mentions, citations, appearances) over a date range for ranking prompts. | | ### Topics & prompts | Tool | What it does | Writes | | - | - | - | | `list_topics` | Topics on a brand with prompt counts — for coverage audits before drilling into prompts. | | | `list_prompts` | Tracked prompts with text, topic, platforms, models, regions, and active flag; filter by topic or active/inactive (`is_active`). | | | `list_topic_suggestions` | Pending AI-generated topic suggestions awaiting accept or dismiss. | | | `accept_topic_suggestion` | Accept one pending suggestion: creates a topic and marks the suggestion added. | **yes** | | `dismiss_topic_suggestion` | Dismiss one pending suggestion so it does not reappear in future batches. | **yes** | | `get_prompt_volumes` | Per-prompt Google and estimated AI search demand and competition for prompts that already have analysis. | | ### Content opportunities | Tool | What it does | Writes | | - | - | - | | `list_content_opportunities` | Content gaps ranked by opportunity score — where the brand is losing visibility. | | | `get_content_opportunity` | Full details for one opportunity, including metrics, intent, competitors, and any existing brief. | | | `generate_content_brief` | Generate or overwrite an AI content brief for an opportunity (LLM cost/latency). | **yes** | | `update_opportunity_status` | Move an opportunity through workflow states (`new`, `reviewed`, `sent`, `in_progress`, `done`, `dismissed`). | **yes** | ### Site audit | Tool | What it does | Writes | | - | - | - | | `get_site_audit_quota` | Monthly Site Audit allowance (`used`, `limit`, `remaining`) before starting a run. | | | `list_site_audits` | Recent audits for a brand (id, url, status, score) to find a past run without the id. | | | `run_site_audit` | Start an AEO/GEO page audit for a URL (consumes a monthly credit; poll `get_site_audit` for results). | **yes** | | `get_site_audit` | Fetch one audit by id — status, scores, signals, and fix recommendations (read-only). | | ### Shopping | Tool | What it does | Writes | | - | - | - | | `list_shopping_cards` | Shopping cards surfaced in AI commercial contexts; filter by role, platform, and region. | | | `get_product_visibility` | Aggregate visibility for one product title across platforms, regions, and dates. | | ### Action Center | Tool | What it does | Writes | | - | - | - | | `list_actions` | Recommended actions for a brand with status, impact, assignee, due date, and task progress. | | | `get_action` | One action in full: payload, triggering signals, tasks (`mode`, `permission`), and event trail. | | | `list_signals` | Detected changes (visibility, citations, traffic) with before/after values and linked `action_id`. | | | `list_action_history` | Closed actions with measured outcomes and before/after validation results. | | | `update_task_status` | Record progress on a task (`in_progress`, `completed`, `skipped`, `failed`); does not execute the task. | **yes** | | `create_improve_action` | Send a prompt's Improve suggestions to Action Center as a `prompt_improve` action (needs a generated suggestion set for the range and a plan with Improve; idempotent on `idempotency_key`). | **yes** | New tools may ship between releases — see `server.ts` and the [project roadmap](https://github.com/ansvisor/ansvisor#whats-next). ## Try it Ask your client things like: * *"List my Ansvisor brands."* * *"What's my visibility score on ChatGPT for the last 7 days?"* * *"Are my topics balanced?" / "Which topic has the most prompts?"* * *"Show me the prompts in my pricing topic."* * *"Who are my top 5 competitors by mention count this month?"* The model picks the right tool, fills in the brand id and filters, and answers in plain English using the returned data. ## Revoking access If a key leaks or you stop using a client, open **Settings → API Keys** and click the trash icon. Clients using that key lose access immediately. ## Transport The server speaks the [MCP Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) transport in stateless mode — every request is self-contained, no session state is kept between calls. This makes it work cleanly behind any HTTP load balancer and on serverless runtimes. # Prompts Source: https://docs.ansvisor.com/guides/prompts Manage tracked prompts and discover new ones with AI-powered suggestions. ## Two tabs The **Prompts** page (`/dashboard/prompts`) has two tabs: * **All Prompts** — flat list of every active prompt with visibility, mention rate, and platform breakdown * **Insights** — volume analytics + AI-powered prompt suggestions ## Adding prompts Three ways: 1. **Manual** — click `+ Add prompt`, type the query, pick platforms/regions/AI models 2. **Suggestions card** (Insights tab) — Ansvisor analyzes your tracked prompts, competitor citations, and topic gaps, then generates 6–12 new prompt ideas with a topic label, reason, and estimated monthly AI volume. Click `+` to add directly to your tracked set. 3. **Bulk import** — paste a list of prompts, one per line, into the bulk-add dialog ## Prompt Suggestions logic The suggestion engine considers four signals: 1. Brand context (name, description, language) 2. Existing tracked prompts (so it doesn't duplicate) 3. Existing topics (so it reuses your topic labels when relevant) 4. Top 12 competitors cited in your last 200 tracked AI responses 5. Volume signal from `prompt_volumes` for tracked prompts (so suggestions skew toward high-volume territory) Output is calibrated: each suggestion comes with `text`, `topic`, `reason`, and a realistic `est_volume`. The reason explains *why* this prompt matters (gap, competitor activity, trend). Suggestions have a 48-hour TTL. Hit **Refresh** on the Suggestions card to regenerate when you've made content changes or want fresh ideas. ## Volume Analysis (Insights tab) For prompts where you've enabled volume analysis, you'll see: * **Estimated monthly AI volume** (rough, derived from Google search volume + AI adoption multiplier) * **Intent classification** (informational, commercial, navigational, transactional) * **Keyword cluster** — the related queries that group with this prompt Click any prompt to see per-platform results with the full AI response, parsed citations, and competitor mention list. ## Best practices * Aim for **20–80 active prompts** per brand. Below 20 you miss coverage; above 80 noise drowns insights. * Mix prompt types: comparison, how-to, best-of, recommendation, alternative, problem-solving * Re-evaluate quarterly: archive prompts that consistently score 0 and bring nothing actionable See which third-party URLs AI engines trust to talk about your brand. # Reports Source: https://docs.ansvisor.com/guides/reports Immutable snapshot reports with an AI executive summary, ready-made templates and vector PDF export. ## What a report is The **Reports** page (`/dashboard/reports`) generates point-in-time snapshots of your brand's AI visibility. Unlike the live dashboards, a report is **immutable**: the numbers are captured at generation time and never change afterwards, even as new tracking results arrive. That makes reports the right tool for anything you need to stand behind later — a monthly review deck, a client deliverable, or a before/after comparison around a campaign. Every report includes an **AI executive summary** on top of the sections you pick, and compares each metric against the **previous period of equal length** (a 30-day report is compared to the 30 days before it). ## Generating a report Click **Generate Report** and pick a template. A template is a default section set — you can toggle any section on or off before generating. | Template | Default sections | Default range | | - | - | - | | **Weekly Visibility Summary** | KPIs, Trend, Share of Voice | Last 7 days | | **Monthly Executive Report** | Every section | Last 30 days | | **Competitor Benchmark** | KPIs, Share of Voice, Competitors, Topics | Last 30 days | | **Citation & Sources Report** | Citations, Citation Evidence | Last 30 days | Then set: * **Report title** — optional; a name for the library list. * **Brand** — the brand to snapshot. * **Date range** — `7d`, `30d`, `90d` presets or a custom from/to range. * **Sections** — the full picker offers: KPIs, Trend, Share of Voice, Competitors, Topics, Prompts, Mention Evidence, Fan-out, AI Traffic, Shopping, Audit Score, Citations and Citation Evidence. The **Shopping** section only appears for brands with Shopping mode enabled. The AI executive summary is always included — it isn't a pickable section. Generated reports land in the library list on the same page, newest first. ## Reading a report Open a report to see: * **AI executive summary** — a short narrative of what changed versus the previous period: the headline visibility movement, mention/citation growth, sentiment and competitor shifts. It is generated once, from the same frozen snapshot the cards render. * **Per-section cards** — one card per section you selected, in a fixed order. Sections you left out are simply absent. * **Deltas** — KPI cards carry change badges against the previous period of equal length. Percentage deltas render as `+X%`; percentage-point deltas (visibility, sentiment) as `+X pts`. Because the snapshot is frozen, a report's numbers can legitimately differ from the live dashboards — the dashboards keep moving, the report doesn't. ## PDF export **Download PDF** renders the report as a vector PDF — text stays selectable and sharp at any zoom, and the file is small enough to attach to an email. The PDF contains the same sections as the web view, including the executive summary. Pull Ansvisor metrics into your own dashboards and reporting stack. # Topics Source: https://docs.ansvisor.com/guides/topics Cluster prompts by theme and see which areas your brand wins or loses. ## What is the Topics page? The **Topics** page (`/dashboard/topics`) groups your tracked prompts by theme so you can see AEO performance at a higher level than individual prompts. Each topic shows: * Topic name and prompt count * Average visibility score across the topic's prompts * 7-day delta * Top competitor cited within the topic * One-click drill-down into the topic detail page ## When to use it * **Editorial planning** — pick the topic with the lowest score and weakest competitor lead; that's your next content sprint * **Reporting to stakeholders** — topic-level stories ("we own EV Range, we're losing on Reliability") are easier to communicate than prompt-level metrics * **Spotting drift** — a topic that drops while others stay flat usually signals a single competitor surge or content rot ## Topic detail page Clicking into a topic shows: * Every prompt in the cluster with per-platform visibility * Aggregated citations across the topic's prompts * Competitor mention frequency within the topic * A "Generate brief" CTA if the topic has open content optimization opportunities ## Editing topics From the Topics page you can: * **Rename** a topic (the underlying prompts stay attached) * **Merge** two topics — useful when AI suggestions create slightly different labels for the same theme * **Delete** a topic (prompts become un-categorized) Keep topic names short (2–4 words) and intent-driven. *"EV Range"* is better than *"Electric Vehicle Driving Distance Comparisons"*. Manage tracked prompts and use the Suggestions card to expand coverage. # Introduction Source: https://docs.ansvisor.com/introduction Track how AI search engines mention your brand and turn AI visibility into a measurable channel. Ansvisor dashboard Ansvisor dashboard ## What is Ansvisor? [Ansvisor](https://www.ansvisor.com) is an open-source, cloud-ready **AI Search Visibility, GEO, & AEO** platform that helps brands measure and improve their visibility across AI-powered search and answer engines. Track prompt visibility, citations, rankings, competitor benchmarks, and brand performance across ChatGPT, Claude, Gemini, Perplexity, Grok, Microsoft Copilot, Google AI Mode, and Google AI Overviews. Ansvisor enables teams to turn AI search into a measurable growth channel with optimization insights, visibility reporting, competitive intelligence, and data-driven content workflows powered by MCP Server. ### What can you do with Ansvisor? * Learn growth opportunities in one click. * Discover Prompt Volumes and get new Prompt and Topic suggestions. * Monitor visibility and citations across major AI engines. * Benchmark competitors and track AI search performance. * Generate GEO & AEO optimization insights and briefs. * Build long-term authority across AI search ecosystems. Get from zero to your first tracked brand in under 10 minutes. The shift from SEO to AEO, and why it matters for your brand. Run Ansvisor on your own infrastructure with Docker Compose. Skip the setup with our managed cloud at ansvisor.com. ## What you get Real-time visibility scores across every major AI engine, with weekly trend analysis. Cluster the queries that drive AI traffic, get prompt suggestions based on competitor citations. Every URL AI engines cite alongside your brand, classified by source type. Real visit data from AI answer engines — platforms, pages, countries. Side-by-side AI visibility against named competitors and gap analysis. AI-generated content briefs delivered to your CMS via webhook. ## Open-source and self-hostable Ansvisor is MIT-licensed. Every feature available on the cloud is unlocked when you self-host — no feature gating, no telemetry. The repo lives at [github.com/ansvisor/ansvisor](https://github.com/ansvisor/ansvisor). # Quickstart Source: https://docs.ansvisor.com/quickstart Track your first brand in under 10 minutes — cloud or self-hosted. ## Pick a path The fastest way to start. No infrastructure, no env files. Create an account at [app.ansvisor.com/sign-up](https://app.ansvisor.com/sign-up). All paid plans include a 14-day free trial. On the onboarding screen, enter your brand name, primary domain, and industry. Ansvisor auto-suggests starter prompts based on your industry — you can edit them or accept all. The first round of AI engine queries runs automatically and completes in 5–15 minutes. You'll receive an email when results are ready. Once tracking finishes, head to **Insights** in the sidebar. You'll see your initial visibility score, platform breakdown, and the prompts where you're cited (or not). For teams who want full control and zero per-seat pricing. ```bash theme={null} git clone https://github.com/ansvisor/ansvisor.git cd ansvisor cp web/.env.example web/.env cp server/.env.example server/.env ``` Fill in Supabase URL/keys, at least one AI provider key, and Cloro API key. See [Self-host → Env variables](/self-host/env-variables) for the full reference. ```bash theme={null} cd supabase supabase db push ``` Or apply `supabase/migrations/*.sql` manually via the Supabase dashboard. ```bash theme={null} docker compose up -d ``` Web is now on `http://localhost:3000`, API server on `http://localhost`. Open `http://localhost:3000`, create the first user account (becomes org admin), then follow the onboarding flow to add your first brand. ## What happens next After your first tracking run completes, the dashboard fills with data. The most useful first walkthrough: Understand what the score means and how it's calculated. See which third-party URLs AI engines trust to talk about you. Generate AI content briefs to close visibility gaps. Benchmark your AI presence against named rivals. **Got stuck?** Open an issue at [github.com/ansvisor/ansvisor/issues](https://github.com/ansvisor/ansvisor/issues) — we read every report. # Docker Compose setup Source: https://docs.ansvisor.com/self-host/docker-compose Run the full Ansvisor stack on your own server in 10 minutes. ## Stack overview The repo ships with two services in `docker-compose.yml`: * **web** — Next.js 16 dashboard, port `3000` * **server** — Express API + workers, port `80` Plus an external dependency: * **Supabase project** — database, auth, storage. You provide URL + keys. ## Prerequisites * Linux host with Docker 24+ and Docker Compose v2 * Open ports `80` (API) and `3000` (web) — or front them with a reverse proxy * A Supabase project ([free tier](https://supabase.com/) works fine) * API keys for at least one AI provider (OpenAI, Anthropic, Google Gemini) ## Steps ```bash theme={null} git clone https://github.com/ansvisor/ansvisor.git cd ansvisor ``` Either use the Supabase CLI: ```bash theme={null} cd supabase supabase link --project-ref YOUR_PROJECT_REF supabase db push ``` Or paste each `.sql` file in `supabase/migrations/` into the Supabase SQL editor in order. ```bash theme={null} cp web/.env.example web/.env cp server/.env.example server/.env ``` Fill in Supabase URL/keys, AI provider keys, and (optional) DataForSEO + Cloro credentials. See [Env variables](/self-host/env-variables) for the full reference. ```bash theme={null} docker compose up -d docker compose logs -f ``` Web is now on `http://localhost:3000`, API on `http://localhost`. Open `http://localhost:3000/sign-up` and register. The first user becomes org admin automatically. ## Production hardening For a real deployment behind a domain (e.g. `app.example.com` + `api.example.com`): * Front both services with **Cloudflare** (proxied) or **Caddy/Nginx** for HTTPS * Use **AWS Security Groups** / firewall rules to restrict origin ports to known IPs * Set `NEXT_PUBLIC_API_URL=https://api.example.com` and `ALLOWED_ORIGINS=https://app.example.com` accordingly * Mount logs to a persistent volume or ship to a logging service For solo deploys we recommend **Cloudflare proxied + Flexible SSL** for the API hostname. Zero certificate management, HTTPS works in 2 minutes. ## Updating to a new release ```bash theme={null} cd ansvisor git pull docker compose up -d --build ``` Migrations under `supabase/migrations/` run forward-only — re-running `supabase db push` is idempotent. ## Splitting web and server across hosts Common setup: web on Vercel, server + workers on a VM. 1. Don't run `docker compose up` for web — deploy `web/` to Vercel directly via Git integration 2. On the VM, run only the server: `docker compose up -d server` 3. In Vercel, set `NEXT_PUBLIC_API_URL=https://api.your-domain.com` 4. In server's env, set `ALLOWED_ORIGINS=https://your-vercel-domain.com` Detailed Supabase project preparation (schema, RLS, triggers). # Environment variables Source: https://docs.ansvisor.com/self-host/env-variables Full reference for every env var across web and server. ## 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) | | `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 | | `CRON_SECRET` | cloud only | Shared secret for Vercel cron → server auth | \* 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) | | `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 | | `AUDIT_LLM_MODEL` | optional | LLM for the Site Audit's LLM-judged signals + AI recommendations (default: `google/gemini-3-flash-preview`) | | `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 | | `CLORO_API_KEY` | optional | Cloro web scraper API key (for ChatGPT/Gemini/Perplexity web scraping) | | `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.15` = 15% of Google volume) | | `CRON_SECRET` | cloud only | Shared secret with web's CRON\_SECRET; protects internal cron endpoints | ## 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_KEY` and `CRON_SECRET`** quarterly 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 How to upgrade your self-hosted instance to a new release. # Supabase setup Source: https://docs.ansvisor.com/self-host/supabase-setup Prepare a Supabase project for Ansvisor — schema, RLS, auth triggers. ## Create the project 1. Go to [supabase.com](https://supabase.com/) and create a new project 2. Pick a region close to your server (latency matters for DB-heavy operations) 3. Save the database password somewhere safe 4. From **Project Settings → API**, copy: * `Project URL` * `anon public` key → goes into `NEXT_PUBLIC_SUPABASE_ANON_KEY` and `SUPABASE_ANON_KEY` * `service_role` key → goes into `SUPABASE_SERVICE_ROLE_KEY` (keep this secret — it bypasses RLS) ## Apply migrations The repo ships SQL migrations under `supabase/migrations/`, plus a generated `supabase/schema.sql` (every migration in one file) for a one-paste fresh install. ```bash theme={null} npm install -g supabase cd supabase supabase link --project-ref YOUR_PROJECT_REF supabase db push ``` 1. Open Supabase dashboard → SQL Editor 2. Paste the contents of `supabase/schema.sql` (the full schema in one file) → Run 3. Verify all tables created under **Database → Tables** `supabase/schema.sql` is a generated convenience for **fresh** installs. The numbered files under `supabase/migrations/` stay the source of truth and the **upgrade** path — an existing install applies only the new migration(s) (or `supabase db push`). Regenerate the consolidated file after adding a migration with `bash supabase/build-schema.sh`. ## What the migrations create * 16 tables (`organizations`, `profiles`, `brands`, `prompts`, `prompt_results`, `topics`, `competitors`, `content_opportunities`, `prompt_suggestions`, `ai_traffic_logs`, `webhook_configs`, etc.) * RLS (Row-Level Security) policies for multi-tenant isolation by `organization_id` * An auth trigger that auto-creates a `profile` row when a new user signs up * The `user_role` enum (`admin`, `manager`, `analyst`, `agency_partner`) * Index optimization for analytics-heavy queries (per-brand, per-day windows) ## Demo data (local only) For local development, the repo ships `supabase/seed.sql` — a small idempotent fixture (one demo organization, brand, prompts, \~120 prompt results across all tracked engines, competitors, content opportunities, AI traffic logs). The Supabase CLI runs it automatically the next time you do: ```bash theme={null} npx supabase db reset ``` Sign in with **`demo@ansvisor.local` / `demo123`** to land on a populated dashboard. The seed only runs against a local Supabase via the CLI; hosted projects are never touched by it. ## Auth configuration In Supabase dashboard → **Authentication → URL Configuration**: * **Site URL**: your web app URL (`http://localhost:3000` for dev, `https://app.example.com` for prod) * **Redirect URLs**: add `/auth/callback` Optionally enable OAuth providers (Google, GitHub) under **Authentication → Providers** if you want social sign-in. ## Storage (optional) If you'll let users upload brand logos, create a public storage bucket called `brand-logos`. The web app handles upload/access automatically once it exists. Full reference for every env var in `web/.env` and `server/.env`. # Upgrading Source: https://docs.ansvisor.com/self-host/upgrading How to safely upgrade your self-hosted Ansvisor instance. ## Standard upgrade ```bash theme={null} cd ansvisor git pull docker compose up -d --build ``` That's it for most releases. Behind the scenes: * Docker rebuilds the changed image(s) * `restart: unless-stopped` ensures containers come back up automatically * Migrations under `supabase/migrations/` are forward-only — re-running `supabase db push` is idempotent ## When migrations are involved If the release introduces new SQL migrations: ```bash theme={null} cd ansvisor git pull cd supabase supabase db push # apply new migrations cd .. docker compose up -d --build ``` Always run migrations **before** restarting containers — new code may expect new tables/columns. ## Major version upgrades When jumping a major version (e.g. v1 → v2), check the release notes on [GitHub Releases](https://github.com/ansvisor/ansvisor/releases) for breaking changes. Common scenarios: * Renamed env variables — update `web/.env` and `server/.env` * Migration that requires manual data backfill — release notes will spell it out * Removed deprecated endpoints — your custom integrations might need updates ## Rollback If an upgrade breaks something: ```bash theme={null} cd ansvisor git checkout v0.1.0 # previous tag docker compose up -d --build ``` Database migrations are **not** automatically reversed. If a new migration introduced a breaking schema change, you may need to manually restore from a Supabase backup taken before the upgrade. **Always take a Supabase backup before upgrading** in production. From the Supabase dashboard: Database → Backups → New backup. Free tier includes daily backups; Pro tier offers point-in-time recovery. ## Watching the changelog Subscribe to release notifications on the GitHub repo: [ansvisor/ansvisor → Watch → Releases only](https://github.com/ansvisor/ansvisor). You'll get an email for each new release with the full changelog.