# Searchable Agent Source: https://docs.searchable.com/advanced/ai-assistant Get instant SEO and AEO recommendations from your AI-powered copilot ## Overview Your AI Copilot Agent is an intelligent assistant that understands your website, business context, and SEO goals. It provides personalized recommendations and can take actions across your entire Searchable workspace. > [!NOTE] > > The AI Copilot is available on all plans, with advanced actions unlocking on **Professional**, **Agency**, and **Custom** tiers. ## What Your AI Copilot Can Do > **Analyze Performance** > > Review your visibility scores, prompt performance, and competitive position > **Recommend Actions** > > Get prioritized, context-aware recommendations based on your data > **Answer Questions** > > Ask anything about your SEO, audit results, or AI visibility > **Execute Tasks** > > Create prompts, generate content, analyze competitors, and more ## How to Access Your AI Copilot is available throughout Searchable: **From the Dashboard:** * Click the **AI Assistant** icon in the top-right corner * Available on any page within your workspace * Maintains context of your current view **From Specific Pages:** * **Pages Tab**: Right-click any page → "Ask AI" for page-specific recommendations * **Issues Tab**: Click any issue → "Get AI Help" for fix guidance * **Prompts Tab**: Select prompts → "Optimize with AI" for improvement suggestions > [!TIP] > > The AI Copilot learns from your knowledge base (created in Step 1 of the onboarding checklist), so train it well for better recommendations! ## Core Capabilities ### 1. Website Analysis Ask your copilot to analyze any aspect of your site: **Example questions:** * "What are my biggest SEO issues right now?" * "Why is my technical score low?" * "Which pages should I optimize first?" * "How do I improve my Core Web Vitals?" **What it does:** * Reviews your latest audit data * Identifies patterns across issues * Prioritizes by impact and effort * Provides step-by-step fix instructions ### 2. Visibility & Competitive Intelligence Get insights into your AI visibility and competitors: **Example questions:** * "How am I performing against competitors?" * "Which prompts are getting the most mentions?" * "Why isn't \[competitor] being cited for \[topic]?" * "What content gaps exist in my category?" **What it does:** * Analyzes visibility reports across 4 AI platforms * Compares your performance to competitors * Identifies opportunity topics * Recommends content strategy ### 3. Prompt Optimization Improve your AI visibility tracking: **Example questions:** * "Can you analyze my prompt performance?" * "Generate 10 new prompts for \[topic]" * "Why aren't my prompts getting mentions?" * "Optimize this prompt: \[your prompt]" **What it does:** * Reviews prompt mention rates * Generates industry-specific prompts * Suggests improvements to existing prompts * Identifies zero-mention opportunities ### 4. Content Strategy Plan and create optimized content: **Example questions:** * "What content should I create next?" * "Generate a blog post about \[topic]" * "How do I optimize my existing article on \[topic]?" * "What keywords should I target?" **What it does:** * Identifies content gaps * Generates AI-optimized articles * Provides SEO and AEO recommendations * Suggests internal linking opportunities ### 5. Integration Assistance Get help with connected platforms: **Example questions:** * "Show me my Google Search Console data" * "What keywords am I ranking for?" * "Analyze my GA4 traffic from AI platforms" **What it does:** * Retrieves data from connected integrations * Correlates metrics across platforms * Provides unified insights * Assists with publishing workflows ### 6. Technical SEO Guidance Understand and fix technical issues: **Example questions:** * "Explain this issue: \[paste issue title]" * "How do I implement structured data?" * "What's causing my slow page speed?" * "Walk me through fixing 404 errors" **What it does:** * Explains technical concepts clearly * Provides code examples and instructions * Estimates impact of fixes * Offers best practices ## Advanced Features ### Context Awareness Your AI Copilot understands: * **Your current page**: Automatically knows where you are in the app * **Your project**: Aware of your domain, industry, and goals * **Your data**: Has access to all your metrics and history * **Your knowledge base**: Trained on your brand information ### Multi-Turn Conversations Build on previous questions: ```text theme={null} You: "What are my critical issues?" Copilot: [Lists 5 critical issues] You: "Explain the first one in detail" Copilot: [Detailed explanation with fix steps] You: "Generate the code for that fix" Copilot: [Provides implementation code] ``` ### Available Actions The copilot can execute tasks on your behalf: * Create new prompts * Generate content outlines and articles * Run SERP analysis * Fetch competitor data * Analyze keyword opportunities * Export reports > **Read-Only Actions** > > Analyze data, answer questions, provide recommendations > **Write Actions** > > Create prompts, generate content, optimize pages (with confirmation) ## Copilot Tiers by Plan | Capability | Starter (\$100) | Professional (\$300) | Agency (\$750) | Custom | | - | - | - | - | - | | **Basic Analysis** | ✓ | ✓ | ✓ | ✓ | | **Prompt Generation** | ✓ | ✓ | ✓ | ✓ | | **Content Creation** | ✓ | ✓ | ✓ | ✓ | | **Integration Data** | — | ✓ | ✓ | ✓ | | **Competitor Analysis** | — | ✓ | ✓ | ✓ | | **Advanced Analytics** | — | ✓ | ✓ | ✓ | | **Batch Actions** | — | Limited | ✓ | ✓ | | **Custom Tools & Workflows** | — | — | Limited | ✓ | ## Best Practices > [!TIP] > > Start conversations with specific questions, not vague requests > [!TIP] > > Reference specific pages, prompts, or issues for targeted help > [!TIP] > > Review AI-generated content before publishing > [!TIP] > > Use copilot for second opinions on strategies > [!TIP] > > Build multi-turn conversations for complex topics > [!WARNING] > > The AI Copilot provides recommendations based on your data and industry best practices. Always verify critical changes before implementing, especially technical modifications. ## Example Workflows ### Morning SEO Check ```text theme={null} You: "What changed overnight? Any critical issues?" Copilot: Analyzes latest data and highlights: - New critical issues (if any) - Significant score changes - Visibility improvements or drops - Recommended immediate actions ``` ### Content Planning Session ```text theme={null} You: "I need content ideas for next month" Copilot: 1. Analyzes your zero-mention prompts 2. Identifies top opportunities 3. Suggests 5-10 article topics 4. Provides target keywords and structure You: "Create an outline for the first topic" Copilot: Generates comprehensive outline with: - SEO-optimized headlines - Section structure - Internal linking suggestions - Target word count ``` ### Issue Resolution ```text theme={null} You: "How do I fix my Core Web Vitals?" Copilot: 1. Reviews your specific CWV metrics 2. Identifies root causes 3. Prioritizes fixes by impact 4. Provides code examples 5. Estimates score improvement You: "Implement the first fix" Copilot: Provides step-by-step implementation guide ``` ## Privacy & Data **What the copilot knows:** * Your project data (scores, issues, prompts, content) * Your knowledge base (brand info you provided) * Your connected integration data * Industry benchmarks and best practices **What stays private:** * All conversations are private to your account * No data shared with external AI models beyond necessary processing * Training data stays within Searchable * You own all generated content and insights ## Conversation Management **Chat History:** * All conversations are saved automatically * Access past conversations from the sidebar * Search through conversation history * Delete conversations anytime **New Conversation:** * Click "New Chat" to start fresh context * Previous conversations remain accessible * Each chat maintains its own context ## Limitations The AI Copilot is powerful but has boundaries: > [!WARNING] > > **What it won't do:** > > * Make changes without your confirmation for write operations > * Access websites outside your verified properties > * Share your data with third parties > * Guarantee specific ranking improvements (provides best practices only) ## Tips for Best Results ### Be Specific **Instead of:** "Help with SEO"\ **Try:** "Why is my technical score 62 and how can I get it to 80?" ### Provide Context **Instead of:** "Fix this issue"\ **Try:** "Explain this issue '\[paste issue title]' and show me how to fix it on a WordPress site" ### Build on Responses **Multi-turn approach:** 1. Ask for analysis first 2. Request detailed explanation 3. Get implementation steps 4. Ask for code examples 5. Request expected impact ### Use for Planning **Strategic questions:** * "Create a 30-day plan to improve AI visibility" * "What should I focus on this week?" * "Prioritize my issues by ROI" * "Build a content calendar for Q1" ## Getting Help If the copilot doesn't have enough context: * Ensure you've completed the knowledge base setup * Verify your project is properly configured * Check that integrations are connected (for integration data) * Try rephrasing your question more specifically ## Next Steps > **[Train Your Agent](https://app.searchable.com/knowledge-base)** > > Update knowledge base for better recommendations > **[Content Generation](/using-searchable/content-generation)** > > Use copilot to create optimized content > **[Working with Prompts](/using-searchable/working-with-prompts)** > > Optimize prompts with AI assistance > **[Site Audits](/using-searchable/site-audits)** > > Get AI help fixing audit issues # Advanced API Usage Source: https://docs.searchable.com/advanced/api-usage Query your AI visibility data and generate reports programmatically with the Searchable API ## Overview Searchable exposes three programmatic surfaces. Pick the one that fits your use case: > **[MCP (AI assistants)](/integrations/mcp)** > > 30+ read tools for Claude, Cursor, and other MCP clients — the richest way to query your data. > **[REST API](/api-reference/introduction)** > > Fetch audits & issues and generate shareable reports with an API key — see the interactive > reference + playground. > **[Ingest API](/setup/rest-api)** > > Send AI-bot traffic events from any language or platform. > [!NOTE] > > API access requires a **paid Searchable plan** — API keys can't be created on the Free plan. ## Authentication Create an API key in **Manage Account → Integrations → API Keys** in your Searchable dashboard. Keys start with `sea_` and are shown only once — store it securely. Send it as a Bearer token on every request: ``` Authorization: Bearer sea_your_key_here ``` | Base URL | `https://app.searchable.com` | | - | - | ### Scopes Each key carries one or more scopes. They're independent grants, not tiers: `write` does not include `read`. | Scope | Grants | | - | - | | `read` | Fetch audits, issues, and visibility data; generate reports | | `write` | Start audits and sitemap syncs | | `admin` | Everything, including every capability | Changing a project's setup (creating or archiving projects, and managing prompts, topics, competitors, the brand profile and opportunities) needs a narrower **capability** instead; see [Capabilities](/api-reference/introduction#capabilities). A new key is read-only. Turn on **Allow write access** when you create it to add `write` and every capability. A key's scopes can't be changed later; see [Write access](/api-reference/introduction#write-access). A key can also be **bound to a single project**. A project-bound key only works against that project; leave a key unbound to use it across all projects you can access. ## Fetch audits + issues Retrieve the latest audit per page, when each was last completed, and optionally the open issues for each page. ``` GET /api/v1/projects/{projectId}/audits?includeIssues=true ``` ```javascript theme={null} const res = await fetch( "https://app.searchable.com/api/v1/projects/PROJECT_ID/audits?includeIssues=true", { headers: { Authorization: "Bearer sea_YOUR_KEY" } }, // requires the `read` scope ); const { summary, audits } = await res.json(); // summary.totalPages — pages with at least one completed audit (not all tracked pages) // summary.lastCompletedAt — newest completed audit across all pages // audits[].openIssues — present only when includeIssues=true ``` ## Share of voice & competitors Brand vs competitor share of voice, and the project's real/tracked competitor roster (also available as the `get_share_of_voice` and `get_competitors` MCP tools). All four endpoints accept the same filter family: `days`, `platform`, `topicId`, `unbranded`/`branded`, `country`/`locationId`. ``` GET /api/mcp/projects/{projectId}/share-of-voice GET /api/mcp/projects/{projectId}/share-of-voice/history GET /api/mcp/projects/{projectId}/competitors GET /api/mcp/projects/{projectId}/competitors/{competitorId} ``` ```javascript theme={null} const res = await fetch( "https://app.searchable.com/api/mcp/projects/PROJECT_ID/share-of-voice?days=30", { headers: { Authorization: "Bearer sea_YOUR_KEY" } }, // requires the `read` scope ); const { brand, competitors, dateRange } = await res.json(); // brand.delta / competitors[].delta are day-over-day (today vs yesterday), // not a comparison against a prior period the length of `days`. // mentions/citations are always null on this endpoint — call // GET .../competitors or GET .../competitors/{competitorId} for those counts. ``` Two things to know on the competitors endpoints: the list view's `mentions`, on every competitor row (mirroring the in-app Competitors list) and on the `brand` block alike, is an **all-time** count that ignores `days` and the filters, while `mentionsInWindow` beside it counts only the answers inside the window that pass the filters (the count `sov` is computed from); and the detail view carries **two distinct metrics** — the top-level `citations` counts inline citations on responses that *mention* the competitor (any domain), while `history[].domainSources` counts source URLs (pages the engines consulted for generation, not inline citations) on the competitor's *own* domain. ## AI Traffic API First-party AI-traffic measurement — crawler visits and AI-referral sessions sourced from the tracker/CDN pipeline (also available through the single consolidated `get_ai_traffic` MCP tool). ``` GET /api/mcp/projects/{projectId}/traffic/overview GET /api/mcp/projects/{projectId}/traffic/bots GET /api/mcp/projects/{projectId}/traffic/referrals GET /api/mcp/projects/{projectId}/traffic/top-cited-pages GET /api/mcp/projects/{projectId}/traffic/sitemap-coverage GET /api/mcp/projects/{projectId}/traffic/human GET /api/mcp/projects/{projectId}/traffic/correlation GET /api/mcp/projects/{projectId}/traffic/attribution GET /api/mcp/projects/{projectId}/traffic/logs ``` ```javascript theme={null} const res = await fetch( "https://app.searchable.com/api/mcp/projects/PROJECT_ID/traffic/overview?days=30&groupBy=botCategory", { headers: { Authorization: "Bearer sea_YOUR_KEY" } }, // requires the `read` scope ); const { groupBy, crawlers, referrals, topPages, dateRange } = await res.json(); // crawlers.total / referrals.total are window totals; byPlatform breaks each // down by AI platform (openai, anthropic, google, perplexity, microsoft, …). // Because groupBy=botCategory, crawlers.byCategory is also present. // topPages is crawl-ranked (top 10) — its `referrals` field is always null; // call top-cited-pages with a platform for per-page AI-referral counts. ``` `overview`, `bots`, and `correlation` accept optional `groupBy=platform|botCategory|botName`. Omitting it preserves the legacy response. `botCategory` adds `byCategory`; `botName` adds `byBot` with `botId`, `botName`, `botVendor`, and `botCategory`; and correlation's `platform` option adds the per-page `byPlatform` split. Existing `byPlatform` arrays on overview and bots remain present for every grouping. These arrays classify **crawl requests only** — correlation does not attribute a referral session to a particular crawler. The public crawler category set is: * `ai_training` — model-training collection, such as GPTBot and ClaudeBot * `ai_search` — AI retrieval/search fetching, such as OAI-SearchBot, Claude-SearchBot, and PerplexityBot * `ai_assistant` — a distinct user-triggered assistant fetch, such as ChatGPT-User, Claude-User, and Perplexity-User * `ai_agent` — autonomous agents acting on a user's behalf * `search_engine` — conventional search indexing The underlying registry currently also stores `ad_inventory`, `ad_landing`, and `agentic-commerce`. Those categories are dashboard-hidden and filtered out of these public crawler aggregates. The registry column is free text, so the stored set can grow; if a new category becomes dashboard-visible, it becomes an additive public output value. That makes `ai_training → ai_search → ai_assistant` available as a per-page crawl funnel. `ai_search` is evidence of retrieval/search crawling, not proof that the page appeared as a citation in an answer; use the citation/visibility APIs for that outcome. `bots` returns a paginated per-page crawler-activity list — `items[]` and `pagination` (`limit`/`offset`/`returnedCount`/`hasMore`/`nextOffset`; `totalCount` is always `null` since the ranking query doesn't cheaply support an exact cross-platform total). `referrals` returns a daily AI-referral session timeseries by platform plus window `totals`. `top-cited-pages` **requires** an explicit `platform` (one of `openai`, `anthropic`, `google`, `perplexity`, `microsoft`) and returns `400` without one. `platform` and `groupBy` are validated strictly: an unrecognized value returns `400 invalid_argument` listing the supported keys, never a silent empty result. AI Traffic endpoints return `409 { code: "traffic_not_connected", message, howToFix }` when the project lacks the source required by that report — `howToFix` links straight to **AI Traffic → Setup** in the dashboard. Connect a source there, then retry. ## Shopping visibility AI shopping/product visibility (Peec-parity) — products surfaced in AI shopping/product-carousel responses (also available as the `get_shopping_visibility` MCP tool, `view=summary|timeseries`, or pass `productId` for detail). ``` GET /api/mcp/projects/{projectId}/shopping GET /api/mcp/projects/{projectId}/shopping/products/{productId} GET /api/mcp/projects/{projectId}/shopping/timeseries ``` ```javascript theme={null} const res = await fetch( "https://app.searchable.com/api/mcp/projects/PROJECT_ID/shopping?days=30", { headers: { Authorization: "Bearer sea_YOUR_KEY" } }, // requires the `read` scope ); const { available, products, summary, dateRange } = await res.json(); // available:false (200, not an error) means no shopping/product data yet — // this is a data-presence state, not a broken integration. It appears // automatically once tracked prompts trigger an AI shopping carousel. // products[].platforms is a single-element array (the product's one // dominant platform) — call the product-detail endpoint below for a real // per-platform breakdown. ``` `productId` is the product's raw title (the `id` returned by the list endpoint above) — percent-encode it in the URL. The detail endpoint returns occurrences, vendor/pricing rows, a **real** per-platform breakdown, and up to 50 recent responses that surfaced the product. Requires a plan with **Shopping Analytics** (Scale or higher) — returns `403 { requiresUpgrade: true }` if the workspace's plan doesn't include it and the project has data to fetch (an empty project still returns `available:false` first). ## Ads Sponsored ads surfaced in AI answers — who is advertising, the actual ad creatives, ad-heavy prompts, and the frequency trend (also available as the `get_ads` MCP tool, `view=advertisers|creatives|prompts|timeseries`). ``` GET /api/mcp/projects/{projectId}/ads/advertisers GET /api/mcp/projects/{projectId}/ads/creatives GET /api/mcp/projects/{projectId}/ads/prompts GET /api/mcp/projects/{projectId}/ads/timeseries ``` ```javascript theme={null} const res = await fetch( "https://app.searchable.com/api/mcp/projects/PROJECT_ID/ads/advertisers?days=30", { headers: { Authorization: "Bearer sea_YOUR_KEY" } }, // requires the `read` scope ); const { advertisers, brand, totals, pagination } = await res.json(); // advertisers are ranked by ad volume; `brand` is your own ad share of voice // and rank among advertisers (null when you're not advertising). adCount / // appearances count ads we scraped in AI answers, not ad-platform impressions. ``` `/ads/advertisers` is offset-paginated (`{limit, offset, returnedCount, totalCount, hasMore, nextOffset}`); pass an advertiser's `advertiserKey` into `/ads/creatives` or `/ads/timeseries` to drill into one advertiser. `/ads/creatives` also accepts `promptId` and a free-text `search`; `/ads/prompts` accepts `sortBy` (`promptText|adUnits|coveragePct|engines|totalResponses`) + `sortDir`. All windows follow `days` (default 30, max 365). Ad data appears once tracked prompts surface sponsored ad units. `promptId` takes a prompt ID or the prompt's text. An ID is used as given — an unrecognized one matches nothing rather than erroring, so an empty result can mean an unresolved filter as well as no captured data — while text that matches no prompt returns `404`. These endpoints take no topic or location filters; use the `get_ads` MCP tool for those. ## Brand profile & domain authority The knowledge-base brand profile plus AI-observed brand facts, and the domain's Moz authority history (also available via the `get_brand_profile` MCP tool, which takes `include: "domain_authority"` for the Moz block). ``` GET /api/mcp/projects/{projectId}/brand-profile GET /api/mcp/projects/{projectId}/domain-authority ``` ```javascript theme={null} const res = await fetch( "https://app.searchable.com/api/mcp/projects/PROJECT_ID/brand-profile", { headers: { Authorization: "Bearer sea_YOUR_KEY" } }, // requires the `read` scope ); const { profile, facts, factCount } = await res.json(); // profile is the user-curated knowledge base (null until onboarding creates // one): brand name, headline, category, positioning, description, topics, // business type, connected entities, competitor geography/context. // facts[] is the latest weekly batch of statements AI platforms actually make // about the brand — filter with ?platform= and ?verificationStatus= // (unverified | pass | fail). ``` `domain-authority` returns the latest Moz snapshot (`domainAuthority`, `pageAuthority`, `spamScore`, `linkingRootDomains`, `fetchedAt`) plus `history[]` over `days` (default 365, max 1095 — snapshots are roughly monthly). It reads the app-maintained cache only and never triggers a live Moz fetch: `latest` is the newest snapshot regardless of the window, and `latest: null` means the app hasn't fetched DA for this project yet. ## Prompt catalog The project's prompt configuration — what it monitors, what's paused, and what the AI has suggested but nobody has reviewed yet (also available as the `list_prompts` MCP tool). This is configuration data, not performance: for per-prompt visibility scores use `/visibility/prompts`. ``` GET /api/mcp/projects/{projectId}/prompts ``` ```javascript theme={null} const res = await fetch( "https://app.searchable.com/api/mcp/projects/PROJECT_ID/prompts?status=tracked", { headers: { Authorization: "Bearer sea_YOUR_KEY" } }, // requires the `read` scope ); const { summary, prompts, pagination } = await res.json(); // summary always reports all three counts: { tracked, untracked, suggested }. // Tracked/untracked prompts carry type, intent, branded flag, topics, // tracked locations, and keyword volume/difficulty. ``` `status` selects the slice: `tracked` (default — actively monitored), `untracked` (paused), `all` (both), or `suggested` — the pending review queue of AI-proposed prompts (from research runs and the agent) that nobody has accepted or rejected yet, each with the `reason` it was proposed and its `source`. Accepted suggestions graduate into the tracked catalog and leave the queue. `topicId` restricts any slice to one topic; `limit` (default 50, max 200) + `offset` paginate with the standard `pagination` shape. ## Prompt answers Raw, per-response AI-answer data for one prompt (Profound-parity) — platform, response date, response text, brand/competitor mentions, and the source URLs the engine consulted (`sources[]` — pages used for generation, not inline citations), one entry per AI response (also available as the `get_prompt_answers` MCP tool). ``` GET /api/mcp/projects/{projectId}/prompts/{promptId}/responses ``` ```javascript theme={null} const res = await fetch( "https://app.searchable.com/api/mcp/projects/PROJECT_ID/prompts/PROMPT_ID/responses?days=30&limit=10", { headers: { Authorization: "Bearer sea_YOUR_KEY" } }, // requires the `read` scope ); const { prompt, responses, pagination } = await res.json(); // responses[].text is the full answer (inline base64 images removed); // responses[].truncated is true only when it hits the 50,000-character safety // cap. citations only includes source-type citations (the same // filter /api/analytics/visibility/query-detail applies). ``` `limit` is clamped to 1–50 (default 10); `?limit=0` clamps to 1, it never silently falls back to the default. `pagination` follows the standard `{limit, offset, returnedCount, totalCount, hasMore, nextOffset}` shape (see [Pagination](/integrations/mcp#pagination)). `promptId` is the prompt's id or, URL-encoded, a unique id prefix or the prompt's text (matched ignoring case, spacing and trailing punctuation). Returns `404` if nothing in the project matches, and `400` `invalid_argument` listing the candidate ids if the text matches several prompts. ## Generate a shareable report Generate and publish a report, and get back its public share URL. Also available as the `generate_report` MCP tool (requires the **write** scope and an explicit `confirm: true`); this REST endpoint is the direct path. ``` POST /api/mcp/projects/{projectId}/reports GET /api/mcp/projects/{projectId}/reports ``` ```javascript theme={null} const res = await fetch("https://app.searchable.com/api/mcp/projects/PROJECT_ID/reports", { method: "POST", headers: { Authorization: "Bearer sea_YOUR_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ reportType: "combined", // "sentiment" | "visibility" | "combined" (default "sentiment") timeRange: "30d", // "7d" | "30d" | "90d" | "365d" (default "30d") status: "published", // "published" | "draft" (default "published") // optional filters: platforms, topicIds, locationIds (string arrays) // optional: title, unbrandedOnly, brandedOnly }), }); const { shareUrl, shareToken, reportType, success } = await res.json(); ``` `POST` requires the `write` scope and a plan that includes report sharing. `GET /reports?limit=20&offset=0` (requires only the `read` scope) lists previously generated reports for the project, newest first — `{ reports: [{id, reportType, title, status, shareUrl, createdAt}], pagination }`. It lists every currently-public report in the project (any project member's, not just reports the calling key's user created); drafts and unshared reports are excluded — a report saved with `status: "draft"` won't appear until published, and one that's later unshared drops off too. ## Period comparison `GET /visibility`, `GET /visibility/topics`, `GET /share-of-voice`, and `GET /sentiment` answer "vs last period" in one call — pass `compare=previous_period` or `compare=previous_year`: ```bash theme={null} curl -H "Authorization: Bearer $SEARCHABLE_API_KEY" \ "https://app.searchable.com/api/mcp/projects/PROJECT_ID/visibility?days=30&compare=previous_period" ``` Windows are whole UTC calendar days and never overlap: `previous_period` spans the same number of calendar days as the current window and ends the day **before** the current window's first day; `previous_year` is the current window's calendar days shifted back exactly 365 days. The `comparison.dateRange` in the response is exactly the range the previous-period query ran over. The response gains an additive `comparison` block — nothing else changes shape: ```json theme={null} { "summary": { "visibilityScore": 45.5, "...": "..." }, "comparison": { "mode": "previous_period", "dateRange": { "from": "2026-06-11T00:00:00.000Z", "to": "2026-07-11T23:59:59.999Z" }, "previous": { "visibilityScore": 40, "totalResponses": 180, "...": "..." }, "delta": { "visibilityScore": 5.5, "totalResponses": 20 } } } ``` `delta` is current minus previous for every metric computable on both sides — a metric that can't be compared is absent, never a fabricated `0`. On `/share-of-voice`, competitor rows additionally carry `previousSov` / `sovDelta` inline (matched by entity display name — the share-of-voice entity identity); on `/visibility/topics`, each topic row carries `comparison.previousMentionedPercentage` / `mentionedPercentageDelta`. On the MCP `get_visibility` tool, `compare` works with `group_by=summary` and `platform`; `prompt` and `location` return `invalid_argument` rather than silently ignoring it. ## Per-URL metrics rollup One page's three AI series in one call — AI-answer activity citing the URL, AI-referral sessions landing on it, and AI-crawler hits fetching it: ```bash theme={null} curl -H "Authorization: Bearer $SEARCHABLE_API_KEY" \ "https://app.searchable.com/api/mcp/projects/PROJECT_ID/pages/metrics?url=/pricing&days=90" ``` `url` is a full URL or a bare path (resolved against the project's domain); a query string is kept, so `/watch?v=abc` and `/watch?v=xyz` are distinct pages. The citation series reports two distinct numbers — `inlineCitations` (the AI answer linked the URL in its text) and `sourceUses` (a model consulted the URL as a source), with daily `sourceUses` buckets. The two traffic series are scoped to the verified project domain the URL's host resolves to and return `available: false` with a `reason`: `traffic_not_connected` when the [LLM Analytics integration](/setup/overview) isn't connected, or `external_url` for a host outside the project's verified domains (external pages get cited too, so the citation series still answers). A citation-lookup failure likewise degrades just that series (`citation_lookup_failed`) rather than reading as a fake zero. ## Query visibility, sources & sentiment For visibility scores, share of voice, sources, sentiment, shopping visibility, AI ads, raw AI answers, AI traffic, and GA4/GSC data, connect the **[MCP server](/integrations/mcp)** — it exposes read-only tools over the same data and handles auth for you. It's the fastest path for read-heavy automation and works directly inside Claude, Cursor, and other assistants. ## Send AI-bot traffic To report request events from your own app or CDN (so crawlers like GPTBot and ClaudeBot show up in your dashboard), use the **[REST Ingest API](/setup/rest-api)**. That endpoint uses a separate ingest key (`sk_live_…`) and a signed edge path — see its page for the full contract. ## Rate limiting Every API key is limited to **600 requests/minute** across the REST surface (`/api/mcp/*`, `/api/v1/*`, and the Looker Studio connector — MCP tool calls don't count against this budget). Two exceptions: `/api/v1/chat` and `/api/v1/chat/stop` don't count against this budget and don't emit the rate-limit headers below (`/api/v1/chat` has its own chat-specific limiter). Every other response carries rate-limit headers, whether it succeeds or errors: ``` RateLimit: "default";r=598;t=42 RateLimit-Policy: "default";q=600;w=60 X-RateLimit-Limit: 600 X-RateLimit-Remaining: 598 X-RateLimit-Reset: 1755000042 ``` `RateLimit`/`RateLimit-Policy` follow the [IETF rate-limit header draft](https://www.ietf.org/archive/id/draft-ietf-httpapi-ratelimit-headers-08.html) (`r` = remaining, `t` = seconds until reset, `q` = quota, `w` = window in seconds). `X-RateLimit-*` is kept alongside for clients that read the older convention; `X-RateLimit-Reset` is an absolute Unix-epoch second. The limiter fails open: if its backing store is briefly unavailable, requests are **allowed** (never refused for infrastructure reasons) and the rate-limit headers are omitted from those responses — don't hard-require the headers in client code. Exceeding the limit returns `429` with a `Retry-After` header (seconds) and a `rate_limited` problem+json body — see the [error reference](/api/errors#rate-limited). ```javascript theme={null} const res = await fetch("https://app.searchable.com/api/mcp/projects", { headers: { Authorization: "Bearer sea_YOUR_KEY" }, }); if (res.status === 429) { const retryAfter = Number(res.headers.get("Retry-After") ?? "60"); // wait retryAfter seconds, then retry } ``` ## Idempotency The write endpoints listed under [Idempotency](/api-reference/introduction#idempotency) — including `POST /reports`, `POST /audits`, and `POST /sitemap/refresh` — accept an `Idempotency-Key` header. Send the same key on a retry (e.g. after a timeout or a dropped connection) and the API replays the original response instead of repeating the side effect (generating a second report, starting a second audit run, or a second sitemap sync): ```javascript theme={null} const idempotencyKey = crypto.randomUUID(); const res = await fetch("https://app.searchable.com/api/mcp/projects/PROJECT_ID/reports", { method: "POST", headers: { Authorization: "Bearer sea_YOUR_KEY", "Content-Type": "application/json", "Idempotency-Key": idempotencyKey, }, body: JSON.stringify({ reportType: "sentiment" }), }); // A retry with the SAME idempotencyKey returns the same body and adds: // X-Idempotent-Replay: true ``` Notes: * Keys are scoped per API key and held for **24 hours**. * Only a **successful (2xx)** response is stored — a failed attempt (4xx/5xx) is never replayed, so retrying after a real failure with the same key simply runs the request again. * Two **concurrent** requests with the same key never both execute: the second gets `409` (`code: "idempotency_in_flight"`) with `Retry-After: 5` while the first is still running. Wait, then retry with the same key — you'll receive the first request's stored response once it completes (or a fresh run if it failed). See the [error reference](/api/errors#idempotency-in-flight). * A stored response body over **100KB** is not cached — the request still runs normally, and the response carries `X-Idempotent-Skipped: body-too-large` instead of `X-Idempotent-Replay`. * Keys are **not** fingerprinted to the request body: reusing a key with a *different* body returns the stored response from the first request. Use a fresh key for each distinct operation. * No `Idempotency-Key` header — the request behaves exactly as before; this is fully opt-in. ## Error handling Full reference with remediation for every error code: **[API Error Reference](/api/errors)**. | Status | Meaning | | - | - | | `400` | Bad request — check parameters / body (`code: "invalid_argument"` and related validation codes) | | `401` | Missing or malformed API key (`code: "unauthorized"`) | | `403` | Insufficient scope, plan gate, quota exceeded, or a paused/pitch project (`missing_scope`, `plan_upgrade_required`, `quota_exceeded`, `project_not_runnable`, `pitch_not_supported`) | | `404` | Project not found, or the key isn't allowed to reach it (`code: "not_found"`) | | `409` | Integration not connected — GSC/GA4 endpoints return `{ code: "gsc_not_connected" \| "ga4_not_connected", message, howToFix }` when the project has no linked Search Console site / GA4 property (connect in **Settings → Integrations**); AI Traffic endpoints return `{ code: "traffic_not_connected", message, howToFix }` when no crawler-log, CDN, or tracker source is connected (connect in **AI Traffic → Setup**). Retry after connecting | | `429` | Rate limit exceeded — see [Rate limiting](#rate-limiting) above (`code: "rate_limited"`, `Retry-After` header) | | `500` | Server error — retry with exponential backoff (`code: "internal_error"`) | | `504` | The query or report generation timed out — retryable (`code: "query_timeout"`) | Every error body is `application/problem+json`: alongside the original `error` string (unchanged, for existing integrations), it now also carries `type`, `title`, `code`, `message`, and `requestId`. See the [error reference](/api/errors) for the full body shape and every code. ## API key best practices > [!TIP] > > Never commit API keys to version control — use environment variables > [!TIP] > > Scope each key to the minimum it needs (leave **Allow write access** off unless it must write) > [!TIP] > > Bind a key to a single project when it only ever touches that project > [!TIP] > > Use separate keys for dev and prod, and rotate them regularly > [!TIP] > > Revoke compromised keys immediately in Settings → Integrations ## Need more? Higher rate limits, custom endpoints, or a dedicated integration? Contact **[support@searchable.com](mailto:support@searchable.com)**. ## Next steps > **[MCP Integration](/integrations/mcp)** > > Connect Claude, Cursor, and other assistants to your Searchable data. > **[REST Ingest API](/setup/rest-api)** > > Send AI-bot traffic events from any stack. # Advanced Custom Prompts (Beta) Source: https://docs.searchable.com/advanced/custom-prompts Master advanced prompt techniques for maximum AI visibility ## Overview Advanced prompt customization allows you to create sophisticated tracking strategies using variables, templates, and automation. ## Features * **Prompt Variables**: Dynamic prompts that adapt to context * **Conditional Logic**: Different prompts based on user segments * **A/B Testing**: Test prompt variations for effectiveness * **Batch Operations**: Manage hundreds of prompts efficiently * **Performance Analytics**: Deep dive into prompt metrics ## Variable System Use variables in prompts: ```text theme={null} What are the best {category} for {audience} in {year}? ``` Variables auto-populate based on your settings. ## Templates & Patterns * Save successful prompts as templates * Apply patterns across multiple prompts * Industry-specific template libraries * Custom template creation ## Automation * Auto-generate prompts from keywords * Schedule prompt activations * Retire underperforming prompts automatically * Smart prompt suggestions ## Custom Capabilities * Multi-language prompt management * Team collaboration on prompts * Advanced attribution modeling * Custom reporting dashboards Learn more in [Working with Prompts](/using-searchable/working-with-prompts). # Teams & Collaboration (Beta) Source: https://docs.searchable.com/advanced/teams-collaboration Manage team members, permissions, and collaborative workflows ## Overview Searchable's team features enable collaborative SEO and AEO management with role-based access control and shared dashboards. ## Team Roles ### Administrator * Full access to all features * Can invite/remove team members * Manage billing and subscriptions * Configure integrations ### Editor * Edit content and settings * Run audits * Manage prompts * View analytics * Cannot manage team or billing ### Viewer * Read-only access * View dashboards and reports * Export data * Cannot make changes ## Managing Team Members **1. Invite Members** Settings → Team → Invite Member * Enter email address * Select role * Send invitation **2. Member Accepts** They receive the email invitation, create an account or sign in, and get access based on their role. **3. Manage Access** * Change roles anytime * Suspend access temporarily * Remove members * Track activity ## Agency & Custom Features > [!NOTE] > > Advanced security features (SSO, audit trails, custom roles) are included with Custom contracts. > Agency customers can request specific capabilities through their success manager. ### SSO/SAML * Single sign-on integration * Azure AD, Okta, Google Workspace * Automatic user provisioning * Centralized access management > [!NOTE] > > **Signing out with SSO.** Signing out of Searchable ends your Searchable session only. It doesn't > sign you out of your identity provider (Okta, Microsoft Entra ID, Google Workspace), so while > you're still signed in there, **Continue with SSO** signs you straight back in without asking for > your password. To end both, sign out of your identity provider too. Signing out of your identity > provider doesn't end an open Searchable session either; an SSO session ends on its own within 12 > hours. ### Advanced Permissions * Custom role creation * Granular feature permissions * Project-level access control * IP whitelisting ### Audit Trails * Complete activity logs * Compliance reporting * Change history * Security monitoring ## Best Practices > [!TIP] > > Assign clear responsibilities per team member > [!TIP] > > Use comments for collaborative decision-making > [!TIP] > > Regular team check-ins on progress > [!TIP] > > Shared optimization priorities ## Next Steps > **[Invite Your Team](https://app.searchable.com/settings/team)** > > Start collaborating > **[Copilot Workflows](/advanced/ai-assistant)** > > See how the AI assistant supports teamwork # Building a Pitch Report Source: https://docs.searchable.com/agencies/building-a-pitch-report Turn your pitch data into a story that wins the deal > [!NOTE] > > Pitches are available on agency plans, and Reports require a Professional plan or higher. See [Plans & Pricing](/getting-started/plans-pricing) for details. ## Why a Pitch Report Matters Raw dashboards don't win deals. A prospect shown forty numbers remembers none of them. A pitch report turns their data into a short, focused story: here's where you're missing from AI answers, here's who's taking your place, and here's what we'd fix first. Plan for about ten minutes of evidence before the conversation takes over. The conversation taking over is the goal. ## Before You Start > [!TIP] > > Your pitch is set up and its first visibility report has completed. See [Setting Up a Pitch](/agencies/setting-up-a-pitch) > [!TIP] > > You've added the rival you suspect is winning as a competitor ## Step 1: Export Your Prompt Data Go to **Prompts** → **Your Prompts** and click **Export** to download your prompt-level data, including visibility for each prompt. This is your working layer. Use it to sort through results while you choose your story, and keep it on hand for detailed questions during the call and for the numbers in your proposal. ## Step 2: Gather Your Evidence Work through these views in **AEO Insights**, in this order. Before you start, set your date range so every view covers the same period. ### Visibility: The Headline Gap Open the **Visibility** tab and compare your prospect with their competitors on visibility, average position, and share of voice. The competitor ranking table puts them side by side, and the **Compare competitors** toggle on the visibility chart plots each rival's trend against your prospect's. Look at the gap, not just your prospect's own number. A visibility score only means something next to the rival who's beating it. Watch the trend lines too: a rival climbing while your prospect stays flat turns "you're behind" into "you're falling behind." Together, these tell you which story you're telling: * **Absent**: your prospect rarely appears in AI answers at all * **Present but weak**: they appear, but usually behind competitors * **Strong but losing ground**: they're doing well, but a rival is catching up > [!TIP] > > Pick one rival to name throughout the pitch, and let the data choose. It should be whoever is actually taking your prospect's answers, which isn't always their biggest traditional competitor. ### Mentions & Citations: Where the Answers Are Lost Open the **Mentions & Citations** tab and find the **Decision Journey** chart. It shows how often your prospect is mentioned at each stage of the buying journey: * **Learn**: missing here means shortlists form without them * **Consider**: missing here means they're left out when buyers compare options * **Purchase**: missing here means ready-to-buy questions are answered with someone else's product Click any stage to see its prompts, then click a prompt to read the full AI answers. Pull two or three real buying questions where your prospect is absent and their rival is named. Verbatim AI answers are the strongest evidence in the whole pitch. ### Topics: The Rival's Strongholds Open the **Topics** tab and look for commercially important topics where your prospect is absent and their rival ranks. One or two are enough. Choose the ones closest to revenue. ### View as Competitor: The Winner's Side Use the brand selector at the start of the filter bar to switch to your prospect's rival. The dashboards redraw from the rival's point of view: their visibility, their buying journey, their topics, and their share of voice. This is often the most memorable moment in a pitch. Your prospect gets to see their competitor's scoreboard, winning. Note which buying stages carry the rival, and which of their strongest topics line up with your prospect's gaps. ### Sources: Why Them, Not Us Open the **Sources** tab to see which websites AI engines cite when answering questions in your prospect's space. Look for the review sites, roundups, and community threads that mention the rival but not your prospect. These are, quite literally, where the rival's answers come from. Every one of those sources is an outreach target or a content gap, which is exactly the work your engagement delivers. ### Sentiment: The Attribute Gap Open the **Sentiment** tab to see how AI answers describe your prospect compared with their competitors. Pay close attention to attributes marked **Behind**, where a competitor is praised for something your prospect isn't mentioned for at all. If the AI consistently describes the rival as the most comfortable option and never attaches comfort to your prospect, that's a positioning loss no brand tracker would show them. > [!TIP] > > Resist completeness. Query Fanout and Location are valuable once you're working together, but in a pitch they use up attention your headline needs. Pick the strongest example from each view, not every example. ## Step 3: Build the Report Click **Export** in the top bar to open **Reports**, and create a new AI Visibility report. Reports are built from data blocks. Add blocks from the **Blocks** sidebar, or ask the report's built-in Agent to build it for you. Keep only what serves your story, in the same order you gathered it: 1. Visibility and share of voice, with the competitor ranking 2. Mentions and the decision journey 3. Sentiment 4. Sources Close on what you'd fix first, not on a list of features. > [!NOTE] > > A report is a snapshot. It won't change when your pitch data updates, so you stay in control of exactly what your prospect sees. Reports created inside a pitch always stay tied to that pitch's data. ## Step 4: Brand and Share It Open the report's **Share** menu to publish it as a link. Before you share: * **Add a password**: hand the password over on the call. It shows your prospect their data is being handled properly * **Choose your branding**: keep Searchable's branding, hide it, or apply your own logo and colors with white-labelling White-labelling is available on higher plans. Your logo, report name, and colors are set once in **All settings** → **White-labelling** by a workspace Owner or Super Admin, and then applied to any report you choose. > [!WARNING] > > The branding option you choose (Searchable, hidden, or your own) locks once a report is published. To switch it, unpublish the report, change the option, then publish it again. Changes to your logo, report name, or colors in White-labelling settings update published reports automatically. You can still change the password at any time. The link is usually the best deliverable, since you control who can open it. You can also download a PDF if your prospect needs one. > [!TIP] > > Choose branding deliberately for each prospect, then keep it consistent for the rest of that relationship. ## Step 5: Tell the Story Bring it all together into one tight story for the call: 1. **You're absent**: the headline gap against the rival you've named 2. **Here's where it hurts**: the buying stages, verbatim AI answers, and the topics closest to revenue 3. **Here's their side**: the rival's view, winning 4. **Here's why**: the sources feeding them and the attributes they own 5. **Here's what we'd do first**: your plan, mapped to exactly those sources, topics, and attributes Your finished pitch pack: * The branded, password-protected report link * Your prompt export, for detailed questions and your proposal * A one-line diagnosis you can open the meeting with, like: "You appear in under a quarter of AI answers about your category, and for your best-selling product line, the AI recommends your competitor by name." ## Common Questions > **Will my report update when my pitch data changes?** > > No. Reports are snapshots, so they stay exactly as you built them. If you'd like to update a published report with the latest data, refresh the snapshot from the **Share** menu. > **Why can't I change the branding on my report?** > > The branding option you choose locks once a report is published. Unpublish it, change the option, then publish it again. Changes to your logo, report name, or colors in White-labelling settings update published reports automatically. > **Why can't I turn on white-labelling?** > > White-labelling is available on higher plans, and needs a logo or brand name set up first in **All settings** → **White-labelling**. Only a workspace Owner or Super Admin can set this up. > **Can I send my prospect a PDF instead of a link?** > > Yes, you can download a PDF of your report. A password-protected link is usually the better choice, since you stay in control of who can see it. ## Next Steps > **[Setting Up a Pitch](/agencies/setting-up-a-pitch)** > > Create a pitch and set it up for a credible diagnosis > **[AI Visibility Tracking](/using-searchable/visibility-tracking)** > > Learn more about the metrics in your pitch # Setting Up a Pitch Source: https://docs.searchable.com/agencies/setting-up-a-pitch Show a prospect their AI visibility before they sign > [!NOTE] > > Pitches are available on agency plans. See [Plans & Pricing](/getting-started/plans-pricing) for details. ## What is a Pitch? A **pitch** is a temporary project you create for a prospective client, so you can show them their AI visibility before they sign. Instead of promising an audit after the sale, you walk into the call with their data already in hand. > [!TIP] > > Run the audit before the sales call, not after it. When a prospect sees their own numbers, you're presenting a diagnosis, not a sales pitch. ## Before You Start > [!TIP] > > Your prospect's website domain > [!TIP] > > Their main competitors, including the one you suspect is winning > [!TIP] > > The topics that matter most to their business > [!TIP] > > Room in your pitch allowance ## How the Pitch Allowance Works Pitches use a separate prompt allowance, shared across all of your active pitches. It doesn't count against your regular plan's prompts or projects, so pitching never eats into your clients' tracking. How large your pitch allowance is depends on your plan. See [Plans & Pricing](/getting-started/plans-pricing). You can see how much allowance you have left on the **Pitches** tab of the All Projects page. Once it's fully used, you won't be able to create new pitches or add prompts to existing ones. Declining pitches you no longer need returns their prompts to your allowance. > [!TIP] > > Keep each pitch focused. Smaller prompt sets mean your allowance can cover more prospects at once. ## Creating a Pitch **1. Open your pitches** Click your current project's name at the top of the sidebar, then click **View all projects** and open the **Pitches** tab. **2. Create a pitch for your prospect** Create a new pitch and enter your prospect's domain. Searchable sets up a pitch project around it. Each domain can only have one pitch. If you've pitched the same domain before, the existing pitch is reactivated rather than created again. **3. Set up their brand profile** Open **Knowledge Base** → **Brand profile** and check your prospect's brand name, along with any other names or spellings it goes by. **4. Add their competitors** In **Knowledge Base** → **Competitors**, add up to five competitors, including the rival you suspect is winning. The comparison is the heart of your pitch, so choose carefully. **5. Choose their topics** In **Knowledge Base** → **Topics**, add topics that cover your prospect's main commercial categories. **6. Build the prompt set** Go to **Prompts** → **Prompt Research** and click **Generate Prompts & Topics**, or add prompts yourself. Aim for a credible diagnosis rather than full coverage: their core category, their highest-value topics, every stage of the buying journey, and mostly unbranded questions. **7. Let the prompts run** Your results appear once the first visibility report completes. After that, prompts run every day. ## What's Included in a Pitch A pitch shows a focused set of features, built for diagnosing a prospect's visibility rather than delivering ongoing work. > **Included** > > AEO Insights, Shopping (when shopping data is captured), Your Prompts, Prompt Research, Reports, the Searchable Agent, and three Knowledge Base tabs: Brand profile, Competitors, and Topics > **Unlocked when you convert** > > LLM Analytics, Traffic, Site Health, Issues, Content, Opportunities, and the rest of Knowledge Base > [!NOTE] > > This is by design, not a plan limit. The pitch shows the gap; the engagement is how you fill it. ## Refreshing Your Data Each pitch includes **5 manual refreshes**. To refresh, click **Refresh** in the filter bar on **AEO Insights**. Spend them on the moments that matter, like the morning of your sales call, or after you add a competitor partway through the pitch. ## How Long a Pitch Lasts Every pitch lasts **7 days**, with a **one-time 7-day extension**. You'll get a reminder email when a pitch is about to expire, and again once it has expired. > [!TIP] > > Treat the time limit as a feature. Create the pitch the week you plan to sell with it, and schedule the call inside that window. ## Ending a Pitch Every pitch ends in one of three ways: * **Convert to client**: the prospect signs. You keep all the data, every feature unlocks, and the prompts move onto your regular plan's allowance. Day one of the engagement starts with a baseline already running. * **Decline**: the deal is off. The prompts return to your pitch allowance for your next prospect. * **Expire**: the time runs out and the data stops refreshing. Once a pitch is no longer active, it's frozen: you can't edit its prompts, topics, or locations, or refresh its data. ### Finding Past Pitches Inactive pitches stay visible, greyed out, in an **Inactive** section of your project dropdown. From there you can open the pitch, reactivate it (when eligible), convert it to a client, or hide it from the menu. You'll also find your full pitch history, including converted, paused, and expired pitches, on the **Pitches** tab of the All Projects page. > [!TIP] > > Hiding a pitch only tidies up the dropdown, for everyone in your workspace. It doesn't change the pitch's status, and you can restore it from the Pitches tab at any time. ## Common Questions > **Why can't I see LLM Analytics, Site Health, or Content in my pitch?** > > Pitches show a focused set of features by design. Everything else unlocks when you convert the pitch to a client. > **Why can't I create a new pitch?** > > **Possible Causes:** > > * Your pitch allowance is fully used > * Your role doesn't allow it. Members and Viewers can't create pitches > > **Solutions:** > > * Decline pitches you no longer need to free up allowance > * Ask a workspace Owner, Super Admin, or Admin to create the pitch > **Do pitch prompts count against my plan?** > > No. Pitch prompts use your separate pitch allowance until you convert the pitch to a client. Once converted, they move onto your regular plan's allowance. > **Can I edit a pitch after it expires?** > > Once a pitch is declined or expires, it's frozen. You can reactivate an expired pitch when eligible, or convert it to a client. > **What happens to the data when I convert a pitch?** > > You keep everything. The pitch becomes a regular project, every feature unlocks, and the prompts start counting against your plan's allowance. ## Next Steps > **[Building a Pitch Report](/agencies/building-a-pitch-report)** > > Turn your pitch data into a story that wins the deal > **[Working with Prompts](/using-searchable/working-with-prompts)** > > Create effective prompts for tracking # Get AI-ad frequency timeseries Source: https://docs.searchable.com/api-reference/ads/get-ai-ad-frequency-timeseries /api-reference/openapi.json get /api/mcp/projects/{projectId}/ads/timeseries Per-advertiser daily ad-frequency trend — one series per top advertiser (brand force-included), each with a { date, adCount } array, plus the filtered-window total ad appearances. `days` defaults to 30, max 365. # List ad-surfacing prompts Source: https://docs.searchable.com/api-reference/ads/list-ad-surfacing-prompts /api-reference/openapi.json get /api/mcp/projects/{projectId}/ads/prompts Which tracked prompts surface the most sponsored ads — per-prompt ad-unit count, ad coverage percent (share of the prompt's responses that carried an ad), response count, and the AI engines that surfaced ads. Server-sorted. `days` defaults to 30, max 365. # List AI-ad advertisers Source: https://docs.searchable.com/api-reference/ads/list-ai-ad-advertisers /api-reference/openapi.json get /api/mcp/projects/{projectId}/ads/advertisers Who is running sponsored ads in AI answers, ranked by scraped ad-appearance volume, plus the brand's own ad share of voice + rank (or null when the brand isn't advertising) and project-wide totals. Offset-paginated. adCount is ads we scraped in AI answers, not ad-platform impressions. `days` defaults to 30, max 365. # List AI-ad creatives Source: https://docs.searchable.com/api-reference/ads/list-ai-ad-creatives /api-reference/openapi.json get /api/mcp/projects/{projectId}/ads/creatives Individual ad creatives (deduped sponsored ad variations) surfaced in AI answers — advertiser, headline, body, image, landing URL, how many scraped responses each appeared in, the prompts + AI models it ran on, and first/last seen. A capped top-N by appearance count (no offset paging). `days` defaults to 30, max 365. # Agent API Source: https://docs.searchable.com/api-reference/agent-api Programmatic access to the Searchable agent Searchable's conversational agent — the same one behind the in-app chat — is available programmatically for approved customers on a case-by-case basis. This isn't part of the self-serve REST surface documented in this reference and isn't listed in the OpenAPI spec. If you have a use case for driving the agent directly from your own application or workflow, contact **[support@searchable.com](mailto:support@searchable.com)** with: * Your workspace and the project(s) involved * What you're trying to build * Expected request volume We'll follow up to scope access and any usage terms. > **[Everything else in this API](/api-reference/introduction)** > > The self-serve REST endpoints — visibility, sources, sentiment, traffic, audits, reports, and more > — are documented in the rest of this reference and available today with a standard `sea_` API key. # List raw AI responses for a prompt Source: https://docs.searchable.com/api-reference/answers/list-raw-ai-responses-for-a-prompt /api-reference/openapi.json get /api/mcp/projects/{projectId}/prompts/{promptId}/responses Raw, per-response AI-answer data for one prompt (Profound-parity) — platform, response date, response text, brand/competitor mentions, and citations, one entry per AI response. text is the full answer (inline base64 images removed); truncated flags the rare cut at a 50,000-character safety cap. Paginated: limit (default 10, max 50) + offset. promptId also accepts a unique id prefix or the prompt's text; returns 404 when nothing in the project matches and 400 when several prompts do. `days` defaults to 30, max 365. # Get an article Source: https://docs.searchable.com/api-reference/articles/get-an-article /api-reference/openapi.json get /api/mcp/articles/{articleId} Full article content + SEO metadata. Checks content_pieces (v2) first, falls back to legacy articles. # List articles Source: https://docs.searchable.com/api-reference/articles/list-articles /api-reference/openapi.json get /api/mcp/projects/{projectId}/articles List content for a project — merges content_pieces (v2) and legacy articles. `status` defaults to `all` (excludes archived). `limit` defaults to 50, max 100. # Get page audits Source: https://docs.searchable.com/api-reference/audits-&-issues/get-page-audits /api-reference/openapi.json get /api/mcp/projects/{projectId}/audits Latest page audit per page (SEO + AEO scores) plus summary.lastCompletedAt. Pass includeIssues=true to nest each page's open issues. # Get page audits (v1 alias) Source: https://docs.searchable.com/api-reference/audits-&-issues/get-page-audits-v1-alias /api-reference/openapi.json get /api/v1/projects/{projectId}/audits Identical contract to `GET /api/mcp/projects/{projectId}/audits` — latest site audit per page + project-level lastCompletedAt. Pass ?includeIssues=true to nest each page's open issues. Requires the `read` scope. # List monitored pages Source: https://docs.searchable.com/api-reference/audits-&-issues/list-monitored-pages /api-reference/openapi.json get /api/mcp/projects/{projectId}/pages Monitored pages with issue counts. # List site issues Source: https://docs.searchable.com/api-reference/audits-&-issues/list-site-issues /api-reference/openapi.json get /api/mcp/projects/{projectId}/issues Site issues grouped by severity. # Refresh the project's sitemap Source: https://docs.searchable.com/api-reference/audits-&-issues/refresh-the-projects-sitemap /api-reference/openapi.json post /api/mcp/projects/{projectId}/sitemap/refresh Re-sync the project's configured sitemap — re-discovers the sitemap and reconciles monitored pages (add/update/remove) + HTTP statuses in the background; returns the coordinator job id. Read the resulting pages on GET /pages. Action endpoint — requires the write scope and write access to the project (a workspace viewer gets 403 `forbidden`), enforces runnable-state, and blocks pitch projects. Supports `Idempotency-Key` replay. # Trigger a site audit Source: https://docs.searchable.com/api-reference/audits-&-issues/trigger-a-site-audit /api-reference/openapi.json post /api/mcp/projects/{projectId}/audits Trigger a site audit (technical + AEO) for the project's pages and return the orchestrator job id; results land on GET /audits. Audits every tracked page by default, or the optional pageIds. Always audits the project's own verified domain. Action endpoint — enforces runnable-state, blocks pitch projects, and consumes the monthly audit quota. Requires the `write` scope and write access to the project (a workspace viewer gets 403 `forbidden`). Supports `Idempotency-Key` replay. # Get brand profile Source: https://docs.searchable.com/api-reference/brand/get-brand-profile /api-reference/openapi.json get /api/mcp/projects/{projectId}/brand-profile The project's brand profile: the knowledge-base profile (name, website, positioning, description, category, topics, reach, language, exact match, disambiguation note, connected entities) plus the AI-observed brand facts, optionally filtered by verification status and platform. # Get domain authority Source: https://docs.searchable.com/api-reference/brand/get-domain-authority /api-reference/openapi.json get /api/mcp/projects/{projectId}/domain-authority The project domain's authority: the latest snapshot plus its history over the requested window. # Update brand profile Source: https://docs.searchable.com/api-reference/brand/update-brand-profile /api-reference/openapi.json patch /api/mcp/projects/{projectId}/brand-profile Update a subset of the project's knowledge-base brand fields (name, headline, description, positioning, category, influence, language, websiteUrl, caseSensitive, brandNameDisambiguation, topics). Does not touch connectedEntities, eeatData, or the project's default location. Requires the `knowledge:manage` capability. A pitch project that is not currently active refuses with 409 `INACTIVE_PITCH_PROMPT_MUTATION`. # Add competitors Source: https://docs.searchable.com/api-reference/competitors/add-competitors /api-reference/openapi.json post /api/mcp/projects/{projectId}/competitors Add 1-20 competitors (each by website domain, optionally named) to the project's real/tracked competitor set. An entry that does not resolve to a website domain, such as a bare company name, is refused with 400 `invalid_argument`. `source: "manual"` with exactly one item also runs the duplicate check (409 conflict) the in-app add-competitor dialog runs. Requires the `competitor:match` capability. A pitch project that is not currently active refuses with 409 `INACTIVE_PITCH_PROMPT_MUTATION`. # Edit a competitor Source: https://docs.searchable.com/api-reference/competitors/edit-a-competitor /api-reference/openapi.json patch /api/mcp/projects/{projectId}/competitors/{competitorId} Edit a tracked competitor as the in-app competitor sheet does: rename it, change its website domain, make it a direct or a SERP competitor, and add or remove the other names it goes by (name variations), which is how its mentions are recognised in AI answers. Changing the domain, or making it direct, re-matches its past mentions in the background. Everything is checked before anything is written: a variation that is another competitor's name is refused with 409 `conflict` (merge the two in the app instead), and removing a name the competitor does not have is refused with 400. A name confirmed as another competitor's variation moves to this one. A body with no fields changes nothing and answers with the competitor and its name variations. Requires the `competitor:match` capability and a manage-tier role, as in the app. A pitch project that is not currently active refuses with 409 `INACTIVE_PITCH_PROMPT_MUTATION`. # Get a competitor Source: https://docs.searchable.com/api-reference/competitors/get-a-competitor /api-reference/openapi.json get /api/mcp/projects/{projectId}/competitors/{competitorId} Full detail for one tracked competitor — the list-view fields plus a per-day history[] and up to 10 recent topPrompts[] it was mentioned in. Two distinct citation metrics: citations (top level) counts every citation on responses that mention the competitor, whatever domain it points at; history[].domainCitations counts citations whose URL is on the competitor's own domain. Returns 404 if competitorId doesn't exist or belongs to a different project. `days` defaults to 30, max 365. # List competitors Source: https://docs.searchable.com/api-reference/competitors/list-competitors /api-reference/openapi.json get /api/mcp/projects/{projectId}/competitors The project's real/tracked competitors (rows in the competitors table) with share of voice, sentiment score, and mention counts. Paginated. sov/sentimentScore/mentionsInWindow are windowed by days and honor the platform/topicId/unbranded/branded/country/locationId filters; mentions is always all-time and ignores days + all filters, on each competitor (mirrors the in-app Competitors list) and on the brand block, each of which carries mentionsInWindow beside it; visibilityScore and citations are null in the list view — see the detail endpoint for those. `days` defaults to 30, max 365. `limit` defaults to 20, max 100. # Get GA4 AI-referral visitors Source: https://docs.searchable.com/api-reference/ga4/get-ga4-ai-referral-visitors /api-reference/openapi.json get /api/mcp/projects/{projectId}/ga4/ai-referrals Per-LLM-host AI Source Visitors from GA4 (ChatGPT, Copilot, Gemini, Perplexity, DeepSeek, …): weekly visitor series + week-over-week delta + total. Use this for AI-referral attribution. `weeks` defaults to 12, max 52. Returns 409 ga4_not_connected if no GA4 property is linked. # Get GA4 top pages Source: https://docs.searchable.com/api-reference/ga4/get-ga4-top-pages /api-reference/openapi.json get /api/mcp/projects/{projectId}/ga4/top-pages GA4 top landing pages by sessions. `weeks` defaults to 12, max 52; `limit` defaults to 20, max 100. Returns 409 ga4_not_connected if no GA4 property is linked. # Get GA4 traffic sources Source: https://docs.searchable.com/api-reference/ga4/get-ga4-traffic-sources /api-reference/openapi.json get /api/mcp/projects/{projectId}/ga4/traffic-sources GA4 traffic sources by channel group + source. `weeks` defaults to 12, max 52; `limit` defaults to 20, max 100. Returns 409 ga4_not_connected if no GA4 property is linked. # Get GA4 traffic trend Source: https://docs.searchable.com/api-reference/ga4/get-ga4-traffic-trend /api-reference/openapi.json get /api/mcp/projects/{projectId}/ga4/traffic-trend GA4 daily traffic trend (users, sessions, pageviews) with totals. `weeks` defaults to 12, max 52. Returns 409 ga4_not_connected if no GA4 property is linked. # Get GSC overview Source: https://docs.searchable.com/api-reference/gsc/get-gsc-overview /api-reference/openapi.json get /api/mcp/projects/{projectId}/gsc/overview Live Google Search Console overview for the project's linked site — total clicks, impressions, CTR, average position for the window, plus an optional previous-period comparison with per-metric deltas. Data lags ~3 days (Google withholds the most recent days). Returns 409 gsc_not_connected if no site is linked. # Get GSC performance by country Source: https://docs.searchable.com/api-reference/gsc/get-gsc-performance-by-country /api-reference/openapi.json get /api/mcp/projects/{projectId}/gsc/countries Live GSC performance grouped by country (clicks, impressions, CTR, average position). `limit` defaults to 100, max 250. Returns 409 gsc_not_connected if no site is linked. # Get GSC performance by device Source: https://docs.searchable.com/api-reference/gsc/get-gsc-performance-by-device /api-reference/openapi.json get /api/mcp/projects/{projectId}/gsc/devices Live GSC performance grouped by device (desktop, mobile, tablet). Returns 409 gsc_not_connected if no site is linked. # Get GSC performance trend Source: https://docs.searchable.com/api-reference/gsc/get-gsc-performance-trend /api-reference/openapi.json get /api/mcp/projects/{projectId}/gsc/performance-trend Live GSC daily performance trend (clicks, impressions, CTR, average position per day) with window totals. Returns 409 gsc_not_connected if no site is linked. # Get GSC query distribution by position Source: https://docs.searchable.com/api-reference/gsc/get-gsc-query-distribution-by-position /api-reference/openapi.json get /api/mcp/projects/{projectId}/gsc/position-buckets Live GSC query distribution by ranking bucket (1-3, 4-10, 11-20, 20+) with clicks + impressions per bucket. Returns 409 gsc_not_connected if no site is linked. # Get GSC quick-win queries Source: https://docs.searchable.com/api-reference/gsc/get-gsc-quick-win-queries /api-reference/openapi.json get /api/mcp/projects/{projectId}/gsc/opportunities Live GSC quick-win queries — ranking positions 4-15 with high impressions and low CTR, highest-impressions first. Title/snippet optimization candidates. `limit` defaults to 20, max 100. Returns 409 gsc_not_connected if no site is linked. # Get GSC top pages Source: https://docs.searchable.com/api-reference/gsc/get-gsc-top-pages /api-reference/openapi.json get /api/mcp/projects/{projectId}/gsc/top-pages Live GSC top pages (page URL, clicks, impressions, CTR, average position), highest-clicks first. `limit` defaults to 25, max 250. Returns 409 gsc_not_connected if no site is linked. # Get GSC top queries Source: https://docs.searchable.com/api-reference/gsc/get-gsc-top-queries /api-reference/openapi.json get /api/mcp/projects/{projectId}/gsc/top-queries Live GSC top search queries (query, clicks, impressions, CTR, average position), highest-clicks first. `limit` defaults to 25, max 250. Returns 409 gsc_not_connected if no site is linked. # Getting Started Source: https://docs.searchable.com/api-reference/introduction Authentication, rate limits, idempotency, pagination, and errors for the Searchable REST API ## Overview The Searchable REST API (`/api/mcp/*` and `/api/v1/projects/{projectId}/audits`) exposes the same AI-visibility, sources, sentiment, traffic, and content data available in the Searchable dashboard and [MCP server](/integrations/mcp), over plain HTTP with a bearer API key. Most endpoints are reads (GET). The ones that change something — creating a report, starting an audit or sitemap sync, and managing projects, prompts, topics, competitors, the brand profile and opportunities — are listed under [Scopes](#scopes) and [Capabilities](#capabilities) below. Like the app's own controls, they act straight away: there's no confirm step. Every endpoint in the left sidebar is interactive — send a real request from the browser using your own API key. > **[Advanced API Usage](/advanced/api-usage)** > > Narrative walkthroughs + curl/JavaScript examples per data area. > **[Error Reference](/api/errors)** > > Every error `code` this API returns, and how to fix it. ## Base URL ``` https://app.searchable.com ``` Every path in this reference (e.g. `/api/mcp/projects`) is relative to that base URL. ## Authentication Create an API key in **Manage Account → Integrations → API Keys** in your Searchable dashboard. Keys start with `sea_` and are shown only once — store it securely. API access requires a paid Searchable plan; keys can't be created on the Free plan. Send it as a bearer token on every request: ``` Authorization: Bearer sea_your_key_here ``` > [!NOTE] > > This REST surface authenticates with a static `sea_` API key only. The MCP server additionally > supports an OAuth 2.1 login flow for interactive AI clients — see [MCP > integration](/integrations/mcp) if you're connecting Claude, Cursor, or another MCP client instead > of calling REST directly. ### Scopes Each key carries one or more scopes. Scopes are independent grants, not tiers — `write` does **not** implicitly include `read`: | Scope | Grants | | - | - | | `read` | GET endpoints — visibility, sources, sentiment, traffic, content, audits, … — plus `POST /reports` (sharing a report needs only `read`, as in the app) | | `write` | The two action endpoints `POST /audits` and `POST /sitemap/refresh` | | `admin` | Every endpoint, including the ones that need a capability | A key that only reads needs `read`. A key that also starts audits or sitemap syncs needs `write` requested alongside `read` (or `admin`, which covers both). A key missing the required scope gets `403 { code: "missing_scope" }` — see [missing\_scope](/api/errors#missing-scope). ### Capabilities The endpoints that change a project's setup need a **capability** rather than `write`. Capabilities are narrower than `write`, and `write` includes none of them, so a key can use only the ones requested for it (or `admin`, which covers them all): | Capability | Endpoints | | - | - | | `project:provision` | `POST /projects` (create a project) | | `project:manage` | `POST /projects/{projectId}/lifecycle` (archive or reactivate a project) | | `prompt:draft` | `POST /prompts`, `PATCH /prompts/{promptId}`, `POST /prompts/sync`, `POST /prompts/{promptId}/revisions/{revision}/restore`, `POST /prompt-generation/import`, `POST /topics`, `PATCH /topics/{topicId}` | | `prompt:activate` | `POST /prompts/{promptId}/tracking` (archive or restore a prompt) | | `competitor:match` | `POST /competitors`, `PATCH /competitors/{competitorId}` | | `knowledge:manage` | `PATCH /brand-profile` | | `opportunity:manage` | `PATCH /opportunities/{opportunityId}` | A key without the capability an endpoint needs gets `403 { code: "missing_scope" }`, the same as a missing scope. ### Write access A key you create is read-only (`read`) unless you turn on **Allow write access** when you create it. That adds `write` and every capability above. `admin` can't be self-served. A key's scopes can't be changed after it's created, so to let an integration write, create a new key with the switch on and revoke the old one. A key can't do more than you can. A write on a project your role only lets you view returns `403 { code: "forbidden" }`, as it would in the app. ### Project binding A key can optionally be **bound to a single project** at creation time. A project-bound key can only ever act on that one project — even a request for a project its owning user can otherwise access in the dashboard returns `404 { code: "not_found" }`, indistinguishable from a project that doesn't exist. Leave a key unbound to use it across every project you can access. Bind a key when handing it to a single-tenant integration; leave it unbound for an integration that manages multiple projects. ## Pagination Two conventions appear across this API, depending on the endpoint: **Offset pagination** (`limit` + `offset`) — the standard shape for most list endpoints: ```json theme={null} { "limit": 20, "offset": 0, "returnedCount": 20, "totalCount": 143, "hasMore": true, "nextOffset": 20 } ``` Pass `nextOffset` as the next request's `offset` until `hasMore` is `false`. `totalCount` is occasionally `null` on endpoints where an exact cross-partition total isn't cheap to compute (e.g. `GET /traffic/bots`) — `hasMore`/`nextOffset` still work correctly in that case. Every offset-paginated endpoint — including `GET /issues`, `GET /opportunities`, and `GET /competitors` — now returns this full block (`hasMore` + `nextOffset` included), so one pagination loop works across the whole API. `GET /competitors` additionally keeps its legacy `total` alias inside `pagination` and its top-level `totalCount`, for backward compatibility. **Cursor pagination** (`cursor` + `nextCursor`) — used by the Sources domain's URL-listing endpoints (`GET /sources/domains/{domain}/urls`, `GET /sources/urls`), which read from a keyset-ordered store: ```json theme={null} { "urls": [/* … */], "totalUrls": 812, "nextCursor": "eyJvZmZzZXQiOjIwfQ", "hasMore": true } ``` Pass the previous response's `nextCursor` as the next request's `cursor`. Omit `cursor` for the first page. Treat the cursor as opaque — don't parse or construct it. `limit` defaults vary by endpoint (typically 20-50). The big list endpoints — cited domains, cited URLs, prompts, opportunities — accept up to **1,000 rows per page** (issues up to 5,000); values above an endpoint's cap are clamped, never rejected. See each operation's description in the sidebar. ## CSV export The five biggest list endpoints — `GET /sources/domains`, `GET /sources/urls`, `GET /prompts`, `GET /issues`, `GET /opportunities` — accept `?format=csv` and return the operation's main row set as a downloadable **UTF-8 CSV** (RFC 4180, BOM-prefixed so Excel handles non-ASCII, and spreadsheet-formula-hardened) instead of the JSON envelope: ```bash theme={null} curl -H "Authorization: Bearer $SEARCHABLE_API_KEY" \ "https://app.searchable.com/api/mcp/projects/PROJECT_ID/sources/urls?limit=1000&format=csv" \ -o cited-urls.csv ``` Pagination parameters apply unchanged — export beyond one page by walking `offset`. Nested JSON fields serialize as minified JSON inside their cell. ## Rate limiting Every API key is limited to **600 requests/minute** across this REST surface (`/api/mcp/*`, `/api/v1/*`, and the Looker Studio connector — MCP *tool calls* don't count against this budget). Every response carries rate-limit headers, whether it succeeds or errors: ``` RateLimit: "default";r=598;t=42 RateLimit-Policy: "default";q=600;w=60 X-RateLimit-Limit: 600 X-RateLimit-Remaining: 598 X-RateLimit-Reset: 1755000042 ``` `RateLimit`/`RateLimit-Policy` follow the [IETF rate-limit header draft](https://www.ietf.org/archive/id/draft-ietf-httpapi-ratelimit-headers-08.html) (`r` = remaining, `t` = seconds until reset, `q` = quota, `w` = window in seconds). `X-RateLimit-*` is kept alongside for clients that read the older convention. The limiter fails open: if its backing store is briefly unavailable, requests are **allowed** and the rate-limit headers are simply omitted from that response — don't hard-require the headers in client code. Exceeding the limit returns `429` with a `Retry-After` header (seconds) and a `rate_limited` problem+json body. ## Idempotency These write endpoints accept an `Idempotency-Key` header: `POST /reports`, `POST /audits`, `POST /sitemap/refresh`, `POST /projects`, `POST /lifecycle`, `POST /prompts`, `PATCH /prompts/{promptId}`, `POST /prompts/sync`, `POST /prompts/{promptId}/tracking`, `POST /prompts/{promptId}/revisions/{revision}/restore` and `POST /prompt-generation/import`. Send the same key on a retry (after a timeout or a dropped connection) and the API replays the original response instead of repeating the side effect: ```javascript theme={null} const idempotencyKey = crypto.randomUUID(); const res = await fetch("https://app.searchable.com/api/mcp/projects/PROJECT_ID/reports", { method: "POST", headers: { Authorization: "Bearer sea_YOUR_KEY", "Content-Type": "application/json", "Idempotency-Key": idempotencyKey, }, body: JSON.stringify({ reportType: "sentiment" }), }); // A retry with the SAME idempotencyKey returns the same body and adds: // X-Idempotent-Replay: true ``` Notes: * Keys are scoped per API key and held for **24 hours**. * Only a **successful (2xx)** response is stored — a failed attempt (4xx/5xx) is never replayed; retrying after a real failure with the same key simply runs the request again. * Two **concurrent** requests with the same key never both execute: the second gets `409 { code: "idempotency_in_flight" }` with `Retry-After: 5` while the first is still running. * Reusing a key with a **different** request body returns `409 { code: "idempotency_conflict" }` rather than replaying the first response. Use a new key for a new request. * A stored response body over 100KB isn't cached — the request still runs normally, and the response carries `X-Idempotent-Skipped: body-too-large` instead of `X-Idempotent-Replay`. * No `Idempotency-Key` header — the request behaves exactly as before. Fully opt-in. ## Errors Every non-2xx response is [`application/problem+json`](https://www.rfc-editor.org/rfc/rfc9457) — a machine-readable envelope that additively extends the API's original `{ error: "..." }` shape, so existing integrations that only read `error` keep working unmodified: ```json theme={null} { "type": "https://docs.searchable.com/api/errors#not_found", "title": "Not Found", "status": 404, "code": "not_found", "error": "Project not found or access denied", "message": "Project not found or access denied", "requestId": "b3f1c2a0-4e9d-4a11-9c3a-8e2f6d1a7c55" } ``` `code` is the field to branch on — it's stable across releases. Full catalog, per-code remediation, and additive fields (`requiresUpgrade`, `retryable`, `current`/`limit`, …): **[API Error Reference](/api/errors)**. Beyond the statuses listed per operation, any endpoint may also return a generic `500` or `502` problem+json with `code: "internal_error"` on an unexpected internal failure (the `502` variant carries `retryable: true`) — treat both as transient and retry with exponential backoff. Every response — success or error — also carries `X-Request-Id`. Include it when contacting **[support@searchable.com](mailto:support@searchable.com)** about a specific request. ## Typed clients There's no official SDK — the OpenAPI spec **is** the contract. Call the API directly with `fetch`/`curl`/your HTTP client of choice, or generate TypeScript types straight from the spec with the MIT-licensed [`openapi-typescript`](https://openapi-ts.dev): ```bash theme={null} npx openapi-typescript https://docs.searchable.com/api-reference/openapi.json -o searchable-api.d.ts ``` That produces a `paths` / `components` type tree covering every endpoint and schema on this page — pair it with a typed `fetch` wrapper (e.g. [`openapi-fetch`](https://openapi-ts.dev/openapi-fetch/)) for end-to-end type safety with no generated SDK to maintain. An official SDK isn't planned unless customer demand shows up. ## Next steps > **[Agent API](/api-reference/agent-api)** > > Programmatic access to the Searchable agent, for approved customers. > **[Changelog](/changelog)** > > What's new on this API, and the deprecation policy. # List opportunities Source: https://docs.searchable.com/api-reference/opportunities/list-opportunities /api-reference/openapi.json get /api/mcp/projects/{projectId}/opportunities Actionable opportunities Searchable surfaced for the project — prioritized fix/write/audit suggestions derived from visibility, sentiment, sources, traffic, prompts, site health, and articles. Read-only. Defaults to active opportunities (pass status or includeResolved=true to widen, source/impact to narrow); returns them in the product's priority order plus global stats. Regular-project feature — pitch projects are rejected (403 pitch_not_supported). # Update an opportunity's status Source: https://docs.searchable.com/api-reference/opportunities/update-an-opportunitys-status /api-reference/openapi.json patch /api/mcp/projects/{projectId}/opportunities/{opportunityId} Mark an opportunity completed or dismissed (optionally with a reason/feedback). Requires a connection minted with the `opportunity:manage` capability (not the bare `write` scope). Opportunities are a regular-project feature: an ACTIVE pitch project refuses with 403 `pitch_not_supported`, matching `GET .../opportunities`. # Archive or reactivate a project Source: https://docs.searchable.com/api-reference/projects/archive-or-reactivate-a-project /api-reference/openapi.json post /api/mcp/projects/{projectId}/lifecycle Archive or reactivate a project, as the app's Archive and Reactivate actions do. `action: "archive"` stops the project running its scheduled checks and keeps its data; `action: "reactivate"` starts them again. A workspace keeps at least one active project, so archiving its only one returns 400 `last_domain`; a pitch project refuses with 403 `pitch_not_supported`. Requires the `project:manage` capability. A stale `expectedRevision` returns 409 `stale_revision`. Supports `Idempotency-Key` replay; a reused key with a different body returns 409 `idempotency_conflict`. # Create a project Source: https://docs.searchable.com/api-reference/projects/create-a-project /api-reference/openapi.json post /api/mcp/projects Create a project and set it up as the app's Add project flow does: the project on the workspace's plan cadence, its competitors, brand profile, default location and onboarding record, counted toward the workspace's project quota. It requires the same details the app asks for (brand name and summary, reach, country, at least one competitor); `generatePrompts: true` starts the app's prompt generation in the background, and the prompts are tracked within the prompt allowance. Requires a connection minted with the `project:provision` capability (not the bare `write` scope); a project-bound credential is refused outright, since creating a project is a workspace-level action. Supports `Idempotency-Key` replay; a reused key with a different body returns 409 `idempotency_conflict` instead of replaying the first project. A duplicate domain in the same workspace returns 409 `duplicate_domain`. # Get a project Source: https://docs.searchable.com/api-reference/projects/get-a-project /api-reference/openapi.json get /api/mcp/projects/{projectId} Get a single project by id. # List projects Source: https://docs.searchable.com/api-reference/projects/list-projects /api-reference/openapi.json get /api/mcp/projects List all projects the API key can reach — every workspace-accessible project (owner, member, or viewer fallback access), or just the key's bound project if it's project-scoped. # List workspaces Source: https://docs.searchable.com/api-reference/projects/list-workspaces /api-reference/openapi.json get /api/mcp/workspaces List the workspaces the API key can reach: your role in each, whether you can create projects there, and the key's projects in each. `POST /api/mcp/projects` takes one of these ids as `workspaceId`. A workspace with API access disabled is left out, and a project-scoped key sees only the workspace holding its project. # Add prompts Source: https://docs.searchable.com/api-reference/prompts/add-prompts /api-reference/openapi.json post /api/mcp/projects/{projectId}/prompts Add 1-10 prompts in one call. They're tracked straight away (`isTracked: true`), as when you add prompts in the app: the project's next scheduled run checks them, and they count toward the prompt allowance. The whole batch is checked against the allowance first; one that does not fit adds nothing and returns 403 `quota_exceeded`, so archive some prompts first or add fewer. Requires a connection minted with the `prompt:draft` capability (not the bare `write` scope). NOT atomic across items: each prompt is created independently, so a failure partway through (for example another write taking the allowance's last room) leaves the earlier items created (`created` in the response says how many). Any `topicIds` must belong to the same project as the prompt; one that does not is refused with 404 `not_found`, indistinguishable from a topic id that does not exist. Supports `Idempotency-Key` replay; a reused key with a different body returns 409 `idempotency_conflict`. # Archive or restore a prompt Source: https://docs.searchable.com/api-reference/prompts/archive-or-restore-a-prompt /api-reference/openapi.json post /api/mcp/projects/{projectId}/prompts/{promptId}/tracking Archive or restore a prompt, as the app's Archive and Restore actions do. `action: "archive"` stops tracking it and keeps its history and metrics; `action: "restore"` tracks it again, within the prompt allowance. Prompts added through this API are tracked already and need no restore. Requires the `prompt:activate` capability. A stale `expectedRevision` returns 409 `stale_revision`; a pitch project that is not currently active refuses with 409 `INACTIVE_PITCH_PROMPT_MUTATION`. Supports `Idempotency-Key` replay; a reused key with a different body returns 409 `idempotency_conflict`. # Bulk-edit prompts Source: https://docs.searchable.com/api-reference/prompts/bulk-edit-prompts /api-reference/openapi.json post /api/mcp/projects/{projectId}/prompts/sync Edit existing prompts in bulk, in place: the round trip for a spreadsheet of prompts. Read them with `GET /projects/{projectId}/prompts` (each row carries its `id` and `currentRevision`; `?format=csv` downloads them), change what you need, and send up to 500 rows back in `prompts`. Each row names a prompt by `promptId` and gives the end state for any of `text`, `isBranded`, `intentCategory`, `topics` (names; the list replaces the prompt's topics, `[]` removes them, names the project lacks are created) and `locations` (the full set, at most 5; a location left out stops being tracked and its history is kept). Omit a field to leave it alone; a row that changes nothing writes nothing. Send each prompt's `expectedRevision`: a prompt edited since you read it comes back as `conflict` instead of being overwritten. Every row of one prompt must be in the same request. A reworded prompt keeps its id and past answers, and its old wording stays as a revision. A batch whose added locations do not fit the prompt allowance is refused with 403 `quota_exceeded` and nothing written; past that, each prompt stands alone and reports its own `outcome`. Result entries carry each `promptId` trimmed and lower-cased, so match ids case-insensitively. New prompts go through `POST /projects/{projectId}/prompts`. `dryRun: true` returns the same plan without writing. A dry run plans the edit but does not check the prompt allowance; adding locations to tracked prompts can still be refused with `quota_exceeded` when you apply. Requires a connection minted with the `prompt:draft` capability, and your own project role must allow the write (otherwise 403 `forbidden`). A pitch project that is not currently active refuses with 409 `INACTIVE_PITCH_PROMPT_MUTATION`. Supports `Idempotency-Key` replay; a reused key with a different body returns 409 `idempotency_conflict`. # Create a topic Source: https://docs.searchable.com/api-reference/prompts/create-a-topic /api-reference/openapi.json post /api/mcp/projects/{projectId}/topics Create a user-authored prompt topic — the same `prompt_topics` row the in-app Topics tab creates. A name one of the project's topics already uses (ignoring case and surrounding spaces; archived topics excepted) is refused with 409 `conflict`, carrying that topic's id as `existingTopicId`. Requires the `prompt:draft` capability. A pitch project that is not currently active refuses with 409 `INACTIVE_PITCH_PROMPT_MUTATION`. # Edit a topic Source: https://docs.searchable.com/api-reference/prompts/edit-a-topic /api-reference/openapi.json patch /api/mcp/projects/{projectId}/topics/{topicId} Edit a prompt topic as the in-app Topics tab does: rename it, change its seed keyword, archive it, or restore an archived one. Archiving takes the topic off every prompt it was on, and restoring it later does not put it back on them. A name another live topic in the project uses (ignoring case and surrounding spaces) is refused with 409 `conflict`, carrying that topic's id as `existingTopicId`. Requires the `prompt:draft` capability. A pitch project that is not currently active refuses with 409 `INACTIVE_PITCH_PROMPT_MUTATION`. # Get prompt catalog Source: https://docs.searchable.com/api-reference/prompts/get-prompt-catalog /api-reference/openapi.json get /api/mcp/projects/{projectId}/prompts The project's prompt catalog: tracked (default), untracked, AI-suggested, or all prompts, with their configuration and a summary count of each status. Paginated. # Get prompt revisions Source: https://docs.searchable.com/api-reference/prompts/get-prompt-revisions /api-reference/openapi.json get /api/mcp/projects/{projectId}/prompts/{promptId}/revisions A prompt's wording history, newest revision first, alongside its current revision number. Read-only — unaffected by the pitch freeze, which only blocks mutations. Bare `read` scope, no capability required. # Get the Prompt Universe brief Source: https://docs.searchable.com/api-reference/prompts/get-the-prompt-universe-brief /api-reference/openapi.json get /api/mcp/projects/{projectId}/prompt-generation/brief Which buyer question types matter at each funnel moment for a business model, which of them are decisive for it, and how many prompts each moment and kind should carry. Planning input only: it calls no model and creates nothing. `count` is clamped by the WORKSPACE's own remaining prompt quota, resolved server-side and never taken from the caller. Returns 404 where the feature is not enabled for the workspace. # Import prompts Source: https://docs.searchable.com/api-reference/prompts/import-prompts /api-reference/openapi.json post /api/mcp/projects/{projectId}/prompt-generation/import Add 1-10 validated Prompt Universe candidates to the project as tracked prompts (`isTracked: true`), as when you add prompts in the app: the project's next scheduled run checks them, within the prompt allowance. Starts no Prompt Universe run and calls no model. THE GATES AND THE ALLOWANCE ARE ALL-OR-NOTHING, THE WRITES ARE NOT: every candidate is checked before anything is written and one refusal refuses the whole batch with 422 `prompt_gates_failed` and nothing created; the whole batch is then checked against the prompt allowance, and one that does not fit is refused with 403 `quota_exceeded` and nothing created. Once writing starts each prompt commits on its own and re-checks your prompt quota, so a batch can still stop partway if another write takes the room. Every response - success or failure - carries `outcomes`, one row per submitted candidate (`created` / `failed` / `not_attempted`); on a partial failure the response keeps the underlying error's own status and code (for example 403 `quota_exceeded`) and adds `created` and `outcomes`, so retry only the rows that are not `created`. Any `topicIds` must belong to the same project as the prompt; one that does not is refused with 404 `not_found`, indistinguishable from a topic id that does not exist. Requires a connection minted with the `prompt:draft` capability (not the bare `write` scope), and your own project role must allow the write. Supports `Idempotency-Key` replay. Returns 404 where the feature is not enabled for the workspace. # Restore prompt wording to an earlier revision Source: https://docs.searchable.com/api-reference/prompts/restore-prompt-wording-to-an-earlier-revision /api-reference/openapi.json post /api/mcp/projects/{projectId}/prompts/{promptId}/revisions/{revision}/restore Restore a prompt's wording to an earlier revision by appending a NEW revision with that text (history is never rewritten). Only wording changes — tracking, topics and locations are untouched. `expectedRevision` is required; a stale value returns 409 `stale_revision`. A pitch project that is not currently active refuses with 409 `INACTIVE_PITCH_PROMPT_MUTATION`. Requires the `prompt:draft` capability. Supports `Idempotency-Key` replay; a reused key with a different body returns 409 `idempotency_conflict`. # Update a prompt Source: https://docs.searchable.com/api-reference/prompts/update-a-prompt /api-reference/openapi.json patch /api/mcp/projects/{projectId}/prompts/{promptId} Edit a prompt's wording, intent, branded flag, or keyword metrics. Does NOT archive or restore it — use `POST .../tracking` for that. Requires the `prompt:draft` capability. `If-Match` (e.g. `"3"`) is the optimistic-concurrency guard: a stale value returns 409 `stale_revision`. A pitch project that is not currently active also refuses with 409 `INACTIVE_PITCH_PROMPT_MUTATION`. Supports `Idempotency-Key` replay; a reused key with a different body returns 409 `idempotency_conflict`. # Get query fanout Source: https://docs.searchable.com/api-reference/query-fanout/get-query-fanout /api-reference/openapi.json get /api/mcp/projects/{projectId}/query-fanout Query fanout — the sub-queries AI models generate when answering tracked prompts. Returns the most frequent fanout queries, per-prompt fanout counts, and a per-model breakdown. Paginated: limit/offset page the per-prompt list; topQueriesLimit bounds the top-N queries. Pass unbranded=true or branded=true to filter by prompt brand state. `days` defaults to 30, max 180. `limit` defaults to 50, max 50. # Get query fanout for a prompt Source: https://docs.searchable.com/api-reference/query-fanout/get-query-fanout-for-a-prompt /api-reference/openapi.json get /api/mcp/projects/{projectId}/query-fanout/{promptId} Detailed query fanout for a single prompt — every sub-query AI models generated for it, deduped with per-query frequency, the models that issued it, and the query type (initial_search | model_query). `days` defaults to 30, max 180. # Generate a shareable report Source: https://docs.searchable.com/api-reference/reports/generate-a-shareable-report /api-reference/openapi.json post /api/mcp/projects/{projectId}/reports Generate and publish a shareable report; returns { shareToken, shareUrl }. Set whiteLabel=true (requires a white-label-entitled plan) to apply workspace branding. Requires the `read` scope, as sharing a report is open to every project member in the app; the workspace's report sharing policy and a plan with report sharing still apply. Supports `Idempotency-Key` replay. # List shared reports Source: https://docs.searchable.com/api-reference/reports/list-shared-reports /api-reference/openapi.json get /api/mcp/projects/{projectId}/reports List previously shared reports for the project, newest first — { reports: [{id, reportType, title, status, shareUrl, createdAt}], pagination }. Only currently-live, published reports are listed (a report saved as a draft, or later unshared, won't appear). Paginated with limit/offset. `limit` defaults to 20, max 100. # Get competitor sentiment comparison Source: https://docs.searchable.com/api-reference/sentiment/get-competitor-sentiment-comparison /api-reference/openapi.json get /api/mcp/projects/{projectId}/sentiment/competitors Head-to-head sentiment comparison of the brand vs competitors — sentiment score and positive/neutral/negative share per entity. Pass unbranded=true or branded=true to filter by prompt brand state. `days` defaults to 30, max 365. `limit` defaults to 10, max 50. # Get sentiment history Source: https://docs.searchable.com/api-reference/sentiment/get-sentiment-history /api-reference/openapi.json get /api/mcp/projects/{projectId}/sentiment/history Brand sentiment trend over time — per-day sentiment score and positive/neutral/negative counts, with an improving/declining/stable trend. Pass unbranded=true or branded=true to filter by prompt brand state. `days` defaults to 90, max 365. # Get sentiment summary Source: https://docs.searchable.com/api-reference/sentiment/get-sentiment-summary /api-reference/openapi.json get /api/mcp/projects/{projectId}/sentiment Brand sentiment summary + per-platform distribution (positive/neutral/negative share, sentiment score). Pass unbranded=true or branded=true to filter by prompt brand state, or promptId to narrow everything (the `compare` window included) to one prompt's answers. `analysis` and `analyses` are the project's weekly write-ups and are not narrowed by `topicId` or `promptId`. `days` defaults to 30, max 365. # Get share of voice Source: https://docs.searchable.com/api-reference/share-of-voice/get-share-of-voice /api-reference/openapi.json get /api/mcp/projects/{projectId}/share-of-voice Brand vs competitor share of voice — mention-share percentage, rank among all tracked entities, and day-over-day (today vs yesterday) point change per entity. mentions/citations are always null here (not computed by the backing service, and not shown by the in-app view either) — use /competitors for those. Pass unbranded=true or branded=true to restrict by prompt brand state. `days` defaults to 30, max 365. # Get share of voice history Source: https://docs.searchable.com/api-reference/share-of-voice/get-share-of-voice-history /api-reference/openapi.json get /api/mcp/projects/{projectId}/share-of-voice/history Daily share-of-voice time series for the brand plus its top competitors (competitor set discovered from the same ranking /share-of-voice uses). `days` defaults to 30, max 365. # Get shopping activation timeseries Source: https://docs.searchable.com/api-reference/shopping/get-shopping-activation-timeseries /api-reference/openapi.json get /api/mcp/projects/{projectId}/shopping/timeseries Daily shopping-activation trend — activation rate, response/product counts, and brand visibility per day. `days` defaults to 30, max 365. # Get shopping product detail Source: https://docs.searchable.com/api-reference/shopping/get-shopping-product-detail /api-reference/openapi.json get /api/mcp/projects/{projectId}/shopping/products/{productId} Full detail for one product — occurrences, vendor/pricing rows, a real per-platform breakdown, and up to 50 recent responses that surfaced it. Requires a plan with Shopping Analytics (Scale or higher). Returns 404 if not found in the window. `days` defaults to 30, max 365. # Get shopping visibility summary Source: https://docs.searchable.com/api-reference/shopping/get-shopping-visibility-summary /api-reference/openapi.json get /api/mcp/projects/{projectId}/shopping AI shopping/product visibility (Peec-parity) — top products surfaced in AI shopping/product-carousel responses, ranked by mention volume, plus an activation-rate summary. Returns available:false (200, not an error) when the project has no shopping data yet. Returns 403 requiresUpgrade:true if the workspace's plan doesn't include Shopping Analytics (Scale+) and there is data to fetch. `days` defaults to 30, max 365. # Get per-URL metrics rollup Source: https://docs.searchable.com/api-reference/site-health/get-per-url-metrics-rollup /api-reference/openapi.json get /api/mcp/projects/{projectId}/pages/metrics One page's three AI series joined in a single call, each as daily buckets: AI-answer citations of the URL, AI-referral sessions landing on it (by platform), and AI-crawler hits fetching it (by bot). `url` is a full URL or a bare path (resolved against the project's domain). The two traffic series degrade to `available: false` when the LLM Analytics integration isn't connected; citations always answer. Default window 90 days (max 365). # Get a cited domain's detail Source: https://docs.searchable.com/api-reference/sources/get-a-cited-domains-detail /api-reference/openapi.json get /api/mcp/projects/{projectId}/sources/domains/{domain} Full detail for a single cited source domain — citation share, trend, top prompts, topics, platforms, and content-type breakdown. # Get a cited page's cached content Source: https://docs.searchable.com/api-reference/sources/get-a-cited-pages-cached-content /api-reference/openapi.json get /api/mcp/projects/{projectId}/sources/urls/{url}/content Cached scraped markdown and entity mentions (brand/competitor) for a cited page. `{url}` must be percent-encoded. Returns 404 if the URL has not been cited in the project's data window. # Get a cited URL's detail Source: https://docs.searchable.com/api-reference/sources/get-a-cited-urls-detail /api-reference/openapi.json get /api/mcp/projects/{projectId}/sources/urls/{url} Citation analytics for a single cited URL — total citations, prompts, mentions, and models that cite it. `{url}` must be percent-encoded. Pass unbranded=true to restrict to non-branded prompts. Returns 404 if the URL has not been cited in the project's data window. `days` defaults to 30. # Get cited article-type breakdown Source: https://docs.searchable.com/api-reference/sources/get-cited-article-type-breakdown /api-reference/openapi.json get /api/mcp/projects/{projectId}/sources/article-types Article/content-type breakdown by URL count and citation volume. `days` defaults to 30. # Get cited content-type distribution Source: https://docs.searchable.com/api-reference/sources/get-cited-content-type-distribution /api-reference/openapi.json get /api/mcp/projects/{projectId}/sources/content-types Content-type distribution of cited sources (Blog, Product page, How-to, etc.) with citation rates and per-competitor breakdowns. `days` defaults to 30. # Get cited-source trend Source: https://docs.searchable.com/api-reference/sources/get-cited-source-trend /api-reference/openapi.json get /api/mcp/projects/{projectId}/sources/trend Citation trend over time for source domains. group='brand' → brand's own cited sources; 'competitors' → competitor cited sources; 'top-domains' (default) → top cited domains overall. Pass unbranded=true to restrict to non-branded prompts. `days` defaults to 30. # Get cited source-type summary Source: https://docs.searchable.com/api-reference/sources/get-cited-source-type-summary /api-reference/openapi.json get /api/mcp/projects/{projectId}/sources/source-types Source-type summary (editorial, social, forum, review, institutional, etc.) showing citation counts and share per type. `days` defaults to 30. # List a domain's cited URLs Source: https://docs.searchable.com/api-reference/sources/list-a-domains-cited-urls /api-reference/openapi.json get /api/mcp/projects/{projectId}/sources/domains/{domain}/urls Cited URLs for a specific domain, with titles and content types. Cursor-paginated. `days` defaults to 30. # List AI responses that cited a domain Source: https://docs.searchable.com/api-reference/sources/list-ai-responses-that-cited-a-domain /api-reference/openapi.json get /api/mcp/projects/{projectId}/sources/domains/{domain}/responses AI responses that cited a specific domain, with the originating prompt, platform, and cited URL. Optionally filter to a specific URL. `days` defaults to 30. # List all cited URLs Source: https://docs.searchable.com/api-reference/sources/list-all-cited-urls /api-reference/openapi.json get /api/mcp/projects/{projectId}/sources/urls All cited URLs across a project, with domain, title, content type, and citation counts. Paginated by `offset` (or the legacy `cursor`). `limit` defaults to 20, max 1000 (over-cap values clamp). `days` defaults to 30. # List cited source domains Source: https://docs.searchable.com/api-reference/sources/list-cited-source-domains /api-reference/openapi.json get /api/mcp/projects/{projectId}/sources/domains Top cited source domains from AI answers — rank, citation counts, usage %, models, and top content types. Paginated and sortable. `days` defaults to 30. `limit` defaults to 20, max 1000 (over-cap values clamp). # Get prompt rankings within a topic Source: https://docs.searchable.com/api-reference/topics/get-prompt-rankings-within-a-topic /api-reference/openapi.json get /api/mcp/projects/{projectId}/topics/prompt-rankings The prompt-level drill-down into one topic: for every prompt assigned to it, the brand's average rank, its visibility, and the competitors ranking above it. Answers which specific questions a weak topic is losing on. `topicId` is required — rankings are scoped to a single topic. # Get topic by platform position heatmap Source: https://docs.searchable.com/api-reference/topics/get-topic-by-platform-position-heatmap /api-reference/openapi.json get /api/mcp/projects/{projectId}/topics/heatmap Average brand mention position per topic x AI platform — where the brand is strong on one engine and absent on another. There is deliberately no platform filter: the platform axis is the report. # Get AI-referral session timeseries Source: https://docs.searchable.com/api-reference/traffic/get-ai-referral-session-timeseries /api-reference/openapi.json get /api/mcp/projects/{projectId}/traffic/referrals First-party AI-referral session timeseries (from the Searchable tracker) — one point per day with a per-platform session breakdown, plus window totals. Returns 409 traffic_not_connected if no source is connected. `days` defaults to 30, max 365. # Get AI traffic overview Source: https://docs.searchable.com/api-reference/traffic/get-ai-traffic-overview /api-reference/openapi.json get /api/mcp/projects/{projectId}/traffic/overview First-party AI-traffic overview (crawler visits + AI-referral sessions), both broken down by platform, plus the top crawled pages. `groupBy=botCategory|botName` adds the requested crawl breakdown without replacing `byPlatform`. Returns 409 traffic_not_connected if no tracker/CDN/log source is connected for the project. `days` defaults to 30, max 365. # Get crawl-to-visit correlation Source: https://docs.searchable.com/api-reference/traffic/get-crawl-to-visit-correlation /api-reference/openapi.json get /api/mcp/projects/{projectId}/traffic/correlation Per page: how often AI crawlers fetched it versus same-window AI-referred human sessions. `groupBy=platform|botCategory|botName` adds the requested crawl-side breakdown; it does not attribute sessions to a crawler. Rows are ranked by AI-referral sessions. `limit` defaults to 50, max 200. # Get human traffic from AI assistants Source: https://docs.searchable.com/api-reference/traffic/get-human-traffic-from-ai-assistants /api-reference/openapi.json get /api/mcp/projects/{projectId}/traffic/human Human sessions that arrived FROM an AI assistant — total sessions and visitors for the window, the AI-referred share of them, and a per-platform split. The conversion half of the funnel that the crawler reports open. Returns 409 traffic_not_connected if no tracker/CDN/log source is connected. # Get raw request logs Source: https://docs.searchable.com/api-reference/traffic/get-raw-request-logs /api-reference/openapi.json get /api/mcp/projects/{projectId}/traffic/logs The raw request feed with the in-app Logs tab's filters (path, status code, bots-only, host, sitemap-only). Cursor-paginated: pass the previous response's `nextCursor.timestamp` and `nextCursor.event_id` back as `cursorTimestamp` and `cursorEventId`. `limit` defaults to 25, max 100. `ip_address` and `user_agent` are deliberately not returned — the IP is personal data with no analytical value once `country` is present, and the user-agent is superseded by the resolved `botName` / `botVendor`. # Get sitemap crawl coverage Source: https://docs.searchable.com/api-reference/traffic/get-sitemap-crawl-coverage /api-reference/openapi.json get /api/mcp/projects/{projectId}/traffic/sitemap-coverage How AI crawlers consume the site's sitemap: per bot, how many times it fetched the sitemap and how often, how many sitemap-listed pages it went on to discover, and the median lag from a page appearing in the sitemap to its first crawl. Use it to answer whether newly published pages are actually reaching AI crawlers. Returns 409 traffic_not_connected if no tracker/CDN/log source is connected. # Get top AI-referral pages for a platform Source: https://docs.searchable.com/api-reference/traffic/get-top-ai-referral-pages-for-a-platform /api-reference/openapi.json get /api/mcp/projects/{projectId}/traffic/top-cited-pages Top pages driving AI-referral traffic for one platform. `platform` is required (one of openai, anthropic, google, perplexity, microsoft) — 400 without one. Returns 409 traffic_not_connected if no source is connected. `days` defaults to 30, max 365. # Get traffic attribution Source: https://docs.searchable.com/api-reference/traffic/get-traffic-attribution /api-reference/openapi.json get /api/mcp/projects/{projectId}/traffic/attribution Traffic attribution by UTM tuple (source / medium / campaign) plus referrer domain and bot category, ranked by request count. `limit` defaults to 50, max 200. # List AI-crawler activity by page Source: https://docs.searchable.com/api-reference/traffic/list-ai-crawler-activity-by-page /api-reference/openapi.json get /api/mcp/projects/{projectId}/traffic/bots Per-page AI-crawler activity — each item is a page with total crawls and a per-platform breakdown (or one platform's count when filtered). `groupBy=botCategory|botName` adds an exact breakdown for the returned page window. Paginated. Returns 409 traffic_not_connected if no source is connected. `days` defaults to 30, max 365. # Answer a diagnostic question in one call Source: https://docs.searchable.com/api-reference/visibility/answer-a-diagnostic-question-in-one-call /api-reference/openapi.json get /api/mcp/projects/{projectId}/investigate Runs a whole multi-read chain server-side and in parallel, returning the measurements together instead of requiring five or six requests. `question=visibility_drop` (score trend + which engines moved vs the previous period + share-of-voice shift + weakest topics), `competitive_position` (standing with ranks and deltas + the topics where a rival leads), or `citation_gap` (citation trend + the domains AI answers cite + per-engine citation share). Returns measurements only, plus an `interpretation` list of what they cannot settle — it never names a cause. A signal whose upstream failed appears in `unavailable` with a reason rather than as a zero. # Get per-prompt visibility Source: https://docs.searchable.com/api-reference/visibility/get-per-prompt-visibility /api-reference/openapi.json get /api/mcp/projects/{projectId}/visibility/prompts Per-prompt visibility breakdown. Paginated — the response `pagination` block returns `hasMore` + `nextOffset`; pass `offset` to page through all prompts. `country` / `locationId` narrow every prompt's figures to the answers from those tracked markets; a prompt with no answers there is not listed. An unknown country code is a 400 `invalid_country`, and a `locationId` that is not a UUID a 400 `invalid_argument`. `days` defaults to 30, max 180. `limit` defaults to 500, max 5000. # Get per-topic visibility Source: https://docs.searchable.com/api-reference/visibility/get-per-topic-visibility /api-reference/openapi.json get /api/mcp/projects/{projectId}/visibility/topics Topic-level visibility metrics. Pass unbranded=true or branded=true to restrict every metric by prompt brand state. `days` defaults to 30. # Get visibility by AI platform Source: https://docs.searchable.com/api-reference/visibility/get-visibility-by-ai-platform /api-reference/openapi.json get /api/mcp/projects/{projectId}/visibility/platforms Visibility broken down by AI platform. The same rows appear inside `GET /visibility` as `platforms[]`; this endpoint returns that breakdown on its own and can narrow to specific platforms. `platform` is comma-separated and resolved through the canonical platform map, so it accepts display names (`ChatGPT`, `AI Overviews`), aliases (`openai`, `microsoft`, `xai`), and raw model ids alike — `platform=chatgpt` matches a stored `chatgpt-4o` without also matching a `copilot-gpt-*` id, which belongs to Copilot. Filter further with `topicId` / `country` / `locationId` (each must be a UUID; a malformed value returns 400 `invalid_argument`), and pass unbranded=true or branded=true to filter by prompt brand state. `days` defaults to 30, max 365. # Get visibility by location Source: https://docs.searchable.com/api-reference/visibility/get-visibility-by-location /api-reference/openapi.json get /api/mcp/projects/{projectId}/visibility/locations Visibility broken down by tracked location (per-country/city), with share of voice, sentiment, avg position, and the change vs the prior equal-length window. Filter with `country` / `locationId` / `platform` / `topicId`. Pass unbranded=true or branded=true to filter by prompt brand state. Returns all locations when unfiltered. `days` defaults to 30, max 365. # Get visibility history Source: https://docs.searchable.com/api-reference/visibility/get-visibility-history /api-reference/openapi.json get /api/mcp/projects/{projectId}/visibility/history Visibility time-series data, one point per report. Pass unbranded=true or branded=true to compute the per-report score and stats by prompt brand state, or `country` / `locationId` to compute them from those tracked markets' answers; under a market filter, a report with no answers there (one from before the market was tracked) is left out rather than drawn as 0. An unknown country code is a 400 `invalid_country`, and a `locationId` that is not a UUID a 400 `invalid_argument`. `days` defaults to 90, max 365. # Get visibility summary Source: https://docs.searchable.com/api-reference/visibility/get-visibility-summary /api-reference/openapi.json get /api/mcp/projects/{projectId}/visibility AI visibility score + platform breakdown. Pass unbranded=true or branded=true to restrict every metric (score, mentions, citations) by prompt brand state. `topicId` narrows every metric, the trend and the `compare` window to one topic's prompts, and `country` / `locationId` to the answers from those tracked markets — the topics and countries `availableFilters` lists. A country the project does not track returns zeroed figures, never the whole project's; an unknown country code is a 400 `invalid_country`, and a `topicId` or `locationId` that is not a UUID a 400 `invalid_argument`. `days` defaults to 30, max 365. # API Error Reference Source: https://docs.searchable.com/api/errors Every error code the Searchable REST API returns, what it means, and how to fix it ## Overview Every error response from the REST API (`/api/mcp/*`, `/api/v1/*`, and the Looker Studio connector) is shaped as [`application/problem+json`](https://www.rfc-editor.org/rfc/rfc9457) — a machine-readable envelope that additively extends the API's original `{ error: "..." }` shape. Existing integrations that only read `error` keep working unmodified. ```json theme={null} { "type": "https://docs.searchable.com/api/errors#not_found", "title": "Not Found", "status": 404, "code": "not_found", "error": "Project not found or access denied", "message": "Project not found or access denied", "howToFix": "...", "requestId": "b3f1c2a0-4e9d-4a11-9c3a-8e2f6d1a7c55" } ``` | Field | Meaning | | - | - | | `type` | A URL identifying the error kind — always this page, anchored to `code` | | `title` | Human-readable summary of `code` | | `status` | The HTTP status code (matches the response status) | | `code` | Machine-readable error code — stable, safe to branch on | | `error` | **Legacy compat key.** Always equal to `message`. Existing integrations that read this keep working | | `message` | Human-readable error message (identical to `error`) | | `howToFix` | Present on actionable errors — a concrete next step | | `requestId` | The request's id. Include this when contacting support | Some errors carry additional fields beyond this base shape (e.g. `requiresUpgrade`, `retryable`, `current`/`limit` on a quota error) — those are documented per-code below and are additive, never replacing the fields above. ## Quick reference | Code | Status | Meaning | | - | - | - | | [`unauthorized`](#unauthorized) | 401 | Missing, malformed, revoked, or expired API key | | [`sign_in_required`](#sign-in-required) | 401 | A public share page's "team only" workspace found no signed-in member session | | [`forbidden`](#forbidden) | 403 | Access denied to the requested resource | | [`missing_scope`](#missing-scope) | 403 | The API key lacks the required scope (`read`/`write`) or capability | | [`plan_upgrade_required`](#plan-upgrade-required) | 403 | The workspace's plan doesn't include this feature | | [`security_policy_denied`](#security-policy-denied) | 403 | The workspace's administrator has switched off API/MCP access, or report sharing | | [`not_found`](#not-found) | 404 | The resource doesn't exist, or the key can't reach it | | [`invalid_argument`](#invalid-argument) | 400 | A query parameter, path parameter, or body field is invalid | | [`invalid_country`](#invalid-country) | 400 | An unrecognized `country` filter value | | [`prompt_gates_failed`](#prompt-gates-failed) | 422 | A prompt candidate failed the import gates — nothing was created | | [`no_eligible_recipients`](#no-eligible-recipients) | 400 | A "team only" workspace: none of the schedule's recipients are members | | [`no_domain`](#no-domain) | 400 | The project has no verified domain configured | | [`no_workspace`](#no-workspace) | 400/403 | The project isn't linked to a workspace | | [`no_pages`](#no-pages) | 400 | No tracked (or matching) pages to audit | | [`bulk_limit_exceeded`](#bulk-limit-exceeded) | 400 | Too many pages requested for a bulk audit on this plan | | [`quota_exceeded`](#quota-exceeded) | 403 | A monthly/concurrent usage quota is exhausted | | [`project_not_runnable`](#project-not-runnable) | 403 | The project is paused or archived — background jobs are blocked | | [`pitch_not_supported`](#pitch-not-supported) | 403 | The feature isn't available on pitch (scoped) projects | | [`last_domain`](#last-domain) | 400 | Archiving would leave the workspace with no active project | | [`stale_revision`](#stale-revision) | 409 | The prompt or project changed since the revision you sent | | [`conflict`](#conflict) | 409 | A competitor or topic with that name or domain already exists | | [`duplicate_domain`](#duplicate-domain) | 409 | The workspace already has a project on that domain | | [`INACTIVE_PITCH_PROMPT_MUTATION`](#inactive-pitch-prompt-mutation) | 409 | A pitch project that isn't active is a frozen snapshot — no edits | | [`rate_limited`](#rate-limited) | 429 | Too many requests — usually the per-key REST limit, or an upstream Google limit | | [`idempotency_in_flight`](#idempotency-in-flight) | 409 | A request with the same `Idempotency-Key` is still running | | [`idempotency_conflict`](#idempotency-conflict) | 409 | An `Idempotency-Key` was reused with a different request body | | [`audit_lock_busy`](#audit-lock-busy) | 409 | Another audit of the project is starting — retry after `Retry-After` | | [`query_timeout`](#query-timeout) | 504 | The query took too long — narrow the request and retry | | [`gsc_not_connected`](#gsc-not-connected) | 409 | No Google Search Console site linked to the project | | [`gsc_site_not_linked`](#gsc-site-not-linked) | 404 | The linked GSC site record doesn't match the project | | [`gsc_access_denied`](#gsc-access-denied) | 403 | Google revoked/denied access — reconnect the account | | [`ga4_not_connected`](#ga4-not-connected) | 409 | No Google Analytics 4 property linked to the project | | [`traffic_not_connected`](#traffic-not-connected) | 409 | No crawler-log, CDN, or tracker source connected | | [`filters_invalid`](#filters-invalid) | 400 | The `filterSpec` payload is malformed or conflicts with legacy filter params | | [`filters_unsupported`](#filters-unsupported) | 400 | A condition names a dimension, operator, or ref this endpoint doesn't support | | [`filters_too_large`](#filters-too-large) | 400 | Too many values, too many conditions, or an oversized encoded payload | | [`filters_not_enabled`](#filters-not-enabled) | 403 | The analytics filters surface isn't enabled for this user and project | | [`filters_flag_unavailable`](#filters-flag-unavailable) | 503 | The filters feature flag couldn't be evaluated | | [`filter_rules_bounds_exceeded`](#filter-rules-bounds-exceeded) | 400 | A request's metric rules exceed the project, rule-count, or resolved-id limits | | [`filter_compile_invariant`](#filter-compile-invariant) | 500 | An internal invariant was violated while compiling filters | | [`internal_error`](#internal-error) | 500 | An unexpected server error — retry, then contact support | ## Authentication & authorization ### `unauthorized` **401.** The request has no `Authorization: Bearer sea_...` header, the key doesn't start with `sea_`, or the key is invalid, revoked, or expired. **Fix:** Check the header is `Authorization: Bearer sea_xxxxx`. Create or rotate the key under **Manage Account → Integrations → API Keys**. ### `sign_in_required` **401.** Returned by the public share endpoints — `GET /api/visibility/shared/{token}`, `GET /api/chat/shared/{token}`, and the `.../verify` routes on both — when the workspace that owns the shared report or conversation has its sharing policy set to "team only" and the request carries no signed-in session belonging to a member of that workspace. **Fix:** Sign in as a member of the workspace that owns the report or conversation, then retry. ### `forbidden` **403.** The authenticated key's user doesn't have access to the requested resource (distinct from a missing scope or a not-found — the resource exists but this key's user isn't a member, their role doesn't allow the change, such as a workspace viewer starting an audit, or the project is inactive). **Fix:** Confirm the key's owning user is a member of the workspace that owns the resource, with a role that can make the change. ### `missing_scope` **403.** The key doesn't carry the scope or capability the endpoint requires. Read endpoints, and generating a report, require `read`; starting an audit or refreshing a sitemap requires `write`; the endpoints that change a project's setup each need a [capability](/api-reference/introduction#capabilities). `admin` covers all of them. **Fix:** A key's scopes can't be changed after it's created. Create a new key under **Manage Account → Integrations → API Keys**, with **Allow write access** on if it needs to make changes, which adds `write` and every capability. Then revoke the old key. See [Write access](/api-reference/introduction#write-access). ### `plan_upgrade_required` **403.** The action is gated behind a plan the workspace isn't on (white-label branding, report sharing, Shopping Analytics, Looker Studio integration, query fanout, …). Often carries `requiresUpgrade: true`. **Fix:** Upgrade the workspace's plan under **Settings → Billing**. ### `security_policy_denied` **403.** The workspace that owns the requested project has API and MCP access switched off by a workspace-level security policy. This is an administered state, not a plan or scope problem: the key is valid, and it keeps working on any other workspace its owner belongs to — only requests that act on the restricted workspace's projects are refused. Never carries `requiresUpgrade`. The same code is returned by the share-creating endpoints — including `POST /api/mcp/projects/{projectId}/reports` and the MCP server's `generate_report` tool (as a tool error result with `error.code`), both of which publish a shareable report — when the workspace's **report sharing** policy is switched off (*"Report sharing is turned off for this workspace."*). A "team only" sharing policy does not refuse creation; it only limits who can open the link. **Fix:** Nothing on your side will clear it. The restriction is set by Searchable for the workspace at the customer's request; contact the workspace's administrator, or Searchable support, to have it lifted. ## Not found & validation ### `not_found` **404.** The resource doesn't exist, or the API key's project binding doesn't allow it to reach the requested project (a project-bound key can never see another project, even one its owning user can access in-app — see [Scopes](/advanced/api-usage#scopes)). Deliberately indistinguishable from "doesn't exist" so existence never leaks to a caller who shouldn't see it. **Fix:** Double-check the id in the URL. If the key is project-bound, confirm you're calling it for its own project. ### `invalid_argument` **400.** A query parameter, path parameter, or request body failed validation (e.g. an out-of-range `days`, a malformed date, an invalid enum value, or a body that fails its schema). The response often carries `details` with the field-level breakdown. **Fix:** Check the parameter/body shape against the endpoint's documented params. ### `prompt_gates_failed` **422.** At least one candidate sent to `POST .../prompt-generation/import` failed the import gates (no em/en dash, no semicolon, the market's spelling, no near-duplicate), so the whole batch was refused and nothing was created. Carries `validation` (what each gate found) and `outcomes` (one row per candidate). **Fix:** Rewrite or drop the refused candidates (`validation` says what each gate found), then import again. ### `no_eligible_recipients` **400.** Returned by `POST /api/reports/pdf` with `mode: "schedule"` when the workspace's sharing policy is "team only" and none of the requested recipients is the workspace owner or a member — a schedule nobody could ever receive is refused rather than created. Nothing is persisted: no schedule and no generated report. A partial list (at least one member) still creates the schedule and lists the non-members in `dropped`. **Fix:** Include at least one workspace member among the recipients, or ask the workspace administrator to relax the sharing policy. ### `invalid_country` **400.** The `country` filter value isn't a recognized ISO code or name. **Fix:** Pass a valid ISO 3166-1 country code (e.g. `US`, `GB`) or `locationId` instead. ### `no_domain` **400.** The project has no verified domain — required before an audit or sitemap sync can run (the audited origin is always derived from the project's own domain, never a caller-supplied URL). **Fix:** Set the project's domain in the dashboard, then retry. ### `no_workspace` **400 or 403.** The project isn't linked to a workspace, so plan/feature checks (Looker Studio, audits) have nothing to evaluate against. **Fix:** This is an account-configuration issue — contact **[support@searchable.com](mailto:support@searchable.com)**. ### `no_pages` **400.** `POST /audits` was called with no tracked pages and no explicit `pageIds`, or every id in `pageIds` didn't match a page in this project. **Fix:** Track at least one page first, or pass valid `pageIds` for this project. ### `bulk_limit_exceeded` **400.** The requested page count for a single `POST /audits` call exceeds the workspace's plan limit. Carries `bulkLimitExceeded: true`, `requested`, and `maxAllowed`. **Fix:** Pass a smaller `pageIds` list, split the audit into multiple calls, or upgrade the plan. ## Quota & runnability ### `quota_exceeded` **403.** A monthly or concurrent usage quota (audits, reports, prompts, …) is exhausted. Carries `quotaExceeded: true`, `current`, and `limit`. Adding prompts checks the whole batch first: a batch that would go over the prompt allowance adds nothing. **Fix:** Wait for the quota to reset (monthly quotas), reduce concurrent usage, or upgrade the plan. For prompts, archive some tracked prompts or add fewer. ### `project_not_runnable` **403.** The project is paused or archived. Background work (audits, sitemap sync, report generation) requires an active, non-archived project. **Fix:** Resume the project in the dashboard before retrying. ### `pitch_not_supported` **403.** The endpoint isn't available for pitch (scoped, pre-conversion) projects — opportunities, audits, sitemap sync, and archiving or reactivating a project are regular-project-only features. **Fix:** This endpoint only supports regular (non-pitch) projects. ## Conflicts & project state ### `last_domain` **400.** Archiving this project would leave its workspace with no active project, and a workspace keeps at least one. **Fix:** Reactivate or create another project in the workspace first, then archive this one. ### `stale_revision` **409.** The prompt or project changed since the revision you sent (`expectedRevision`, or `If-Match` when editing a prompt). Carries `currentRevision`. **Fix:** Read the current state, check the change still makes sense, then retry with `currentRevision`. ### `conflict` **409.** The item already exists: a competitor with that domain or name (when adding one with `source: "manual"`), or a topic with that name. A topic conflict carries the existing topic's id as `existingTopicId`. **Fix:** Use the existing competitor or topic, or choose a different name. ### `duplicate_domain` **409.** The workspace already has a project on that domain. **Fix:** Use the existing project, or create the new one in a different workspace. ### `INACTIVE_PITCH_PROMPT_MUTATION` **409.** The project is a pitch that is no longer active (declined, expired, archived or accepted), so it's a frozen review snapshot: its prompts, topics, competitors and brand profile can't be changed. The body is `{ error, code }`, not the problem format above. **Fix:** Make the change on an active project instead. ## Rate limiting & timeouts ### `rate_limited` **429.** Usually the per-API-key REST rate limit (600 requests/minute) was exceeded; on Google-backed endpoints (`/gsc/*`) it can also relay Google's own rate limit. `Retry-After` (seconds) is present when Searchable's limiter applied the limit and may be absent for an upstream limit; both carry `retryable: true`. See [Rate limiting](/advanced/api-usage#rate-limiting). Over MCP there is no 429: a tool returns `rate_limited` once a key or OAuth connection exceeds 600 tool calls per minute, with `retryable: true` and `retryAfterSeconds` in the error instead of a `Retry-After` header. The source-scout cooldown from `manage_associated_source` (where Associated Sources is enabled) also returns `rate_limited` over MCP, without `retryable`. **Fix:** Over REST, back off for `Retry-After` seconds when present (otherwise about a minute), then retry. Over MCP, retry the tool-call limit after `retryAfterSeconds`, but never retry the source-scout cooldown automatically. Spread bursts of requests across a longer window rather than firing them all at once. ### `idempotency_in_flight` **409.** Another request carrying the same `Idempotency-Key` is still executing — the duplicate was refused instead of running the side effect a second time. Carries a `Retry-After` header (5 seconds) and `retryable: true`. See [Idempotency](/advanced/api-usage#idempotency). **Fix:** Wait `Retry-After` seconds, then retry **with the same key** — once the original completes successfully you'll receive its stored response (`X-Idempotent-Replay: true`); if the original failed, the retry runs fresh. ### `idempotency_conflict` **409.** The `Idempotency-Key` was already used for a request with a different body, so replaying the stored response would answer a different request. See [Idempotency](/advanced/api-usage#idempotency). **Fix:** Use a new key for a different request; reuse a key only to retry the identical request. ### `audit_lock_busy` **409.** `POST /audits` waited briefly for another audit of the same project (started in the app or through the API) to finish starting, and it was still starting. Nothing was started. Carries a `Retry-After` header (2 seconds) and `retryable: true`. The response is not stored against an `Idempotency-Key`. **Fix:** Wait `Retry-After` seconds, then retry the same request (the same key is fine). Pages the other audit started come back counted in `skippedInFlightCount`. ### `query_timeout` **504.** The query took longer than the endpoint's timeout budget. Carries `timeout: true` and `retryable: true`. **Fix:** Narrow the `days` window or add filters (platform, topic, location), then retry. ## Google integrations ### `gsc_not_connected` **409.** No Google Search Console site is linked to this project. **Fix:** Connect Google Search Console under **Settings → Integrations**, then retry. ### `gsc_site_not_linked` **404.** The project has a GSC site reference that doesn't resolve — a narrower variant of "not connected" surfaced by some GSC endpoints. **Fix:** Reconnect Google Search Console under **Settings → Integrations**. ### `gsc_access_denied` **403.** Google denied the request — the connected account's token was revoked, permission to the property was removed, or the property was unshared. Distinct from `gsc_not_connected`: the integration was connected, but Google is now rejecting it. **Fix:** Reconnect the Google account under **Settings → Integrations**. ### `ga4_not_connected` **409.** No Google Analytics 4 property is linked to this project. **Fix:** Connect Google Analytics 4 under **Settings → Integrations**, then retry. ## AI Traffic ### `traffic_not_connected` **409.** No crawler-log, CDN, or Searchable-tracker data source is connected for this project. **Fix:** Connect a source under **AI Traffic → Setup**. ## Analytics filters These codes come from the Searchable app's own session-authenticated routes. Analytics routes (the dashboard's data endpoints, and the report snapshots built from them) accept a `filterSpec` query parameter; every other session-authenticated app route refuses one with `filters_unsupported`. The REST API surfaces the rest of this page covers never return these codes. ### `filters_invalid` **400.** The `filterSpec` query parameter is malformed — undecodable, an unrecognized version, an unknown dimension or operator, the same dimension named twice in one AND-group, a mix of a legacy per-dimension param with `filterSpec` for the same dimension (or with an OR-joined `filterSpec`), the `filterSpec` parameter repeated, or a join the endpoint doesn't allow. **Fix:** Check the filter payload's encoding and make sure you're not sending both a legacy filter param and a `filterSpec` condition for the same dimension. ### `filters_unsupported` **400.** The decoded filter set names a dimension, operator, or ref this endpoint doesn't support, or the endpoint doesn't support `filterSpec` at all. **Fix:** Check the endpoint's documented supported dimensions and operators, and drop anything else from the request. ### `filters_too_large` **400.** The filter set exceeds a bound — too many values in one condition, too many conditions, or the URL-encoded `filterSpec` payload exceeds the 10 KB size limit. **Fix:** Reduce the number of values or conditions, or split the request. ### `filters_not_enabled` **403.** Filters beyond the basic "is any of" aren't enabled for this user on this project. Requests using only "is any of" conditions are never refused this way. **Fix:** This is a rollout gate, not a plan limit — contact **[support@searchable.com](mailto:support@searchable.com)** if you believe it should be enabled. ### `filters_flag_unavailable` **503.** The feature flag controlling analytics filters couldn't be evaluated. **Fix:** Retry the request. If it persists, contact **[support@searchable.com](mailto:support@searchable.com)**. ### `filter_rules_bounds_exceeded` **400.** A request's metric rules (Your Prompts volume, difficulty, visibility score and similar) exceed a per-request bound: they span more than one project, there are too many metric rules in one request, or a rule resolves to more ids than the cap. Reserved — no endpoint returns it yet. **Fix:** Apply metric rules to a single project, use fewer of them, or narrow each rule, then retry. ### `filter_compile_invariant` **500.** An internal invariant was violated while compiling filters into a query (an unexpectedly empty value set, an unmapped dimension, or an unsupported operator reaching the compiler). Never caused by anything in your request. **Fix:** Retry. If it persists, contact **[support@searchable.com](mailto:support@searchable.com)** with the `requestId`. ## Server errors ### `internal_error` **500.** An unexpected server-side failure. Never leaks internal error text — the message is always a stable, generic description. **Fix:** Retry with exponential backoff. If it persists, contact **[support@searchable.com](mailto:support@searchable.com)** with the `requestId` from the response body. ## Need more? Higher rate limits, a code not listed here, or a question about a specific response? Contact **[support@searchable.com](mailto:support@searchable.com)** with the endpoint, `requestId`, and response body. # Changelog Source: https://docs.searchable.com/changelog Updates to the Searchable REST API and MCP server **October 2026** MCP tool-call limit ## MCP tool calls are now rate limited MCP tool calls are limited to 600 per minute per API key or OAuth connection. Over the limit, a tool returns `rate_limited` with `retryable: true` and `retryAfterSeconds`; wait that long, then retry. The source-scout cooldown from `manage_associated_source` (where Associated Sources is enabled) also returns `rate_limited`, without `retryable`; don't retry that one automatically. REST API limits are unchanged, and normal interactive use from Claude, Cursor or similar clients won't reach the limit. See [Rate limits](/integrations/mcp#rate-limits). **October 2026** API keys with write access ## Create API keys that can make changes Creating an API key or an MCP access token now offers **Allow write access**. It's off by default, so new keys stay read-only. Switched on, the key carries `write` and every capability, so it can use the endpoints and MCP tools that change a project: prompts, topics, competitors, the brand profile, opportunities, projects, audits and sitemap syncs. It can only change what your own role allows. A key's access can't be changed after it's created, so create a new key to add write access. See [Write access](/api-reference/introduction#write-access). **October 2026** Slack and Microsoft Teams agents ## Ask Searchable from Slack and Microsoft Teams Searchable's MCP server now accepts sign-ins from Slackbot and Microsoft Copilot Studio, and lets Microsoft 365 Copilot load its interactive cards. The new [Slack and Teams guide](/integrations/slack-and-teams) covers Claude Tag, Slackbot, Copilot Studio and Microsoft 365 Copilot. It also shows how to give each Slack channel its own single-project, read-only access token, and how to schedule Searchable checks from a channel. **October 2026** Visibility by market ## Split visibility by market `GET /projects/{projectId}/visibility` now honors `country` and `locationId`, the location filter the app's AEO Insights tabs use. The visibility score, the totals, the per-platform rows, the trend, the `compare` window and the `include=industry_ranking` leaderboard all cover only the answers from those tracked markets. It also honors `topicId`. Topics and countries are the options the response's `availableFilters` already listed, and the endpoint used to ignore both. `GET /projects/{projectId}/visibility/history` and `GET /projects/{projectId}/visibility/prompts` take `country` and `locationId` too. Under a market filter, history leaves out a report with no answers in that market (one from before you started tracking it) rather than drawing it as 0. The `get_visibility` MCP tool's `group_by=prompt` view now applies `country` and `locationId` as well. `country` takes ISO codes or country names, comma-separated (`country=GB,US` covers both). A country the project doesn't track matches no answers, so its figures come back zeroed rather than the whole project's. An unknown country code is a 400 `invalid_country`, and a `locationId` that isn't a UUID is a 400 `invalid_argument`, as is a malformed `topicId` on `/visibility`. **October 2026** Sentiment for a single prompt ## See how AI answers describe your brand for one prompt `GET /projects/{projectId}/visibility/prompts` now returns `sentimentScore` in each prompt's `metrics`: how that prompt's answers describe your brand, from 0 to 100 (positive counts 100, neutral 50, negative 0). It covers the same answers as the row's other metrics, the latest completed report in the window, and is `null` when no answer mentions your brand. `GET /projects/{projectId}/sentiment` takes a new `promptId` parameter. With it, the score, the positive/neutral/negative split, the per-platform breakdown and the `compare` window cover only that prompt's answers. A `promptId` that isn't a UUID is a 400 `invalid_argument`. The weekly `analysis` write-ups stay project-wide. **October 2026** Mentions inside the window on the competitors list ## Mention counts that follow your window and filters `GET /projects/{projectId}/competitors` now returns `mentionsInWindow` beside `mentions` on every competitor row and on the `brand` block. It counts the AI answers inside the `days` window that mention each one, and honors `platform`, `topicId`, `unbranded`, `branded`, `country` and `locationId` the same way share of voice does: it is the count each row's `sov` is computed from. `mentions` is unchanged, still every answer across the project's whole history, ignoring `days` and every filter. The `get_competitors` MCP tool returns the same field, and its competitor table gains a `mentionsInWindow` column. **October 2026** Bulk-edit prompts without losing their history ## Edit prompts in bulk and keep their history `POST /projects/{projectId}/prompts/sync` and the `update_prompts` MCP tool edit existing prompts in place. Send up to 500 rows in `prompts`, each naming a prompt by `promptId` with the end state for any of `text`, `isBranded`, `intentCategory`, `topics` and `locations`; a field you leave out is left alone, and a row that changes nothing writes nothing. A reworded prompt keeps its id and its past answers, and the old wording stays in its revision history. Pass each prompt's `expectedRevision` to have a prompt edited since you read it reported as `conflict` instead of overwritten, and `dryRun: true` to preview the changes. `GET /projects/{projectId}/prompts` rows and the `list_prompts` MCP tool now include each prompt's `currentRevision`, so a list you export carries what the edit needs. In the `format=csv` download it is a new last column, so the existing columns keep their positions. **October 2026** Brand mentions on the competitors list ## Your brand's mentions sit beside your competitors' `GET /projects/{projectId}/competitors` now returns `mentions` on the `brand` block as well as on every competitor row, so your own row in a competitor table has a mentions figure too. It is counted the same way as the competitors' `mentions`: the number of AI answers that have mentioned your brand across the project's whole history, ignoring `days` and every filter. The `get_competitors` MCP tool's structured output carries the same field. **September 2026** Prompt answers return the full response text ## Read whole AI answers, not the first 2,000 characters `GET /projects/{projectId}/prompts/{promptId}/responses` and the `get_prompt_answers` MCP tool used to cut every answer's `text` at 2,000 characters, which clipped about half of all ChatGPT answers and nearly every Copilot answer. `text` now carries the full answer. Inline base64 images that some engines embed in their answers are removed, and `truncated` is `true` only for a runaway answer past a 50,000-character safety cap. In the MCP tool, `response_format: "detailed"` now prints every answer in full. The default concise table still shows a short snippet per answer and points to `detailed` when a snippet cuts one off. **September 2026** Name a prompt by its text ## Prompt-scoped calls accept the prompt's text `get_prompt_answers`, `get_query_fanout` (`prompt_id`) and `get_ads` (`promptId`, `view="creatives"`), and their REST routes, now take the prompt's text or a unique prefix of its id as well as the full id. Text is matched within the project ignoring case, spacing and trailing punctuation, with exact matches first. A value matching several prompts returns `invalid_argument` listing their ids, and one matching none returns `not_found` pointing to `list_prompts`. Before, the prompt's text failed with a generic error (in `get_ads`, it silently matched nothing). A full id is still used as given. `list_prompts` now prints each prompt's full `id`, and `get_prompt_answers` takes an `offset` so its "call again with offset" hint pages through the answers. **August 2026** Bot-category and named-bot crawl breakdowns ## Follow each page from training crawl to assistant fetch The existing AI Traffic reports now expose the bot identity Searchable already resolves internally. Pass `groupBy=botCategory` or `groupBy=botName` on REST `/traffic/overview`, `/traffic/bots`, or `/traffic/correlation`, or use the same `groupBy` argument on the consolidated `get_ai_traffic` MCP tool. No new MCP tool was added, and omitting `groupBy` keeps existing response shapes unchanged. The public categories are `ai_training`, `ai_search`, `ai_assistant`, `ai_agent`, and `search_engine`. In particular, `ai_assistant` distinguishes user-triggered fetches such as ChatGPT-User, Claude-User, and Perplexity-User from training and retrieval crawlers. Correlation also supports `groupBy=platform`. Every correlation grouping describes crawl requests; it does not attribute an AI-referred session to a crawler. **August 2026** Charts render only when you ask for them ## `render` — decide per call whether a chart is drawn Nine MCP tools ship an interactive card (`get_visibility`, `get_share_of_voice`, `get_competitors`, `get_sentiment`, `get_topic_analysis`, `get_source_trends`, `get_shopping_visibility`, `get_site_health`, `list_prompts`). Until now a host drew that card beside **every** answer those tools gave, including the many calls that only fetch numbers to summarise or feed into another request. Those nine tools now take an optional `render` boolean: ``` get_visibility(projectId, render: true) // draws the chart card get_visibility(projectId, render: false) // data only, no card get_visibility(projectId) // draws the card, as before ``` `true` when the result is meant to be looked at; `false` when you only need the numbers. State it on every call — but **omitting it is safe and always draws the card**, so nothing that worked before this release behaves differently. > [!NOTE] > > Backwards compatible by design. Clients that pin a tool schema — the ChatGPT plugin directory > publishes a snapshot taken at scan time — keep calling without `render` and keep getting their > cards, unchanged. No REST endpoint is affected; this applies to MCP tool calls only. **August 2026** investigate — one call that answers a whole diagnostic question ## Ask a question, not five queries `investigate` runs a whole diagnostic chain **server-side and in parallel**, returning the measurements together instead of requiring five or six requests you then have to join. Available as an MCP tool and as `GET /api/mcp/projects/{projectId}/investigate`. | `question` | What it returns | | - | - | | `visibility_drop` (default) | Score trend with the largest single-day fall, per-platform movement vs the previous window, share-of-voice shift with the competitors who gained, weakest topics | | `competitive_position` | Your share and rank with period movement, the competitor table, and the topics where a rival currently leads | | `citation_gap` | Your citation trend, the domains AI answers actually cite in your space, and per-engine citation share | It takes the same narrowing arguments as the individual reads — `days` or `from`/`to`, `platform`, `topicId`, `unbranded` / `branded`, `country` / `locationId`. **It reports measurements, never a cause.** It will tell you ChatGPT fell 38 points while Gemini held; it will not tell you why, because the reason is usually outside the data — a rebrand, a campaign ending, a competitor launch. Every response carries an `interpretation` list naming what the numbers cannot settle, and a signal whose upstream failed appears in `unavailable` with a reason rather than as a zero, so missing data is never mistaken for a finding. **Recipes.** Each question has a companion guide covering how to read the result, the interpretation rules, and what the data cannot tell you: [why did our AI visibility drop](/recipes/visibility-drop), [where do we stand against competitors](/recipes/competitive-position), [why aren't we being cited](/recipes/citation-gap). Load one in-chat with `read_doc("recipes/visibility-drop")`. **Slash commands.** The same three playbooks are also exposed as MCP **prompts** — the first prompts this server offers — so clients that render them (Claude Desktop and similar) get `visibility-drop`, `competitive-position`, and `citation-gap` as user-invocable commands. **August 2026** Absolute date ranges, five new traffic reports, and new views on the existing families Everything here is **additive** — no existing call changes shape, and every capability lands on both the MCP tools and the matching REST endpoints, which share one implementation. **Absolute date ranges (`from` / `to`).** Previously only Search Console accepted explicit dates; everything else spoke in relative `days`, so "compare March against June" or "the week of the launch" could not be expressed. Every windowed read now takes `from` and `to` (inclusive, UTC, `YYYY-MM-DD` or full ISO): `get_visibility` (all views), `get_share_of_voice`, `get_sentiment`, `get_topic_analysis`, `get_ai_traffic`, and their REST twins. Both bounds are required together and are mutually exclusive with `days` — sending both returns `400 invalid_argument` rather than silently picking one. An over-long `days` is still clamped; an over-long explicit range is **rejected**, because narrowing a range you spelled out would answer a different question. **Five new AI Traffic reports** on `get_ai_traffic` (and as REST endpoints under `/traffic/*`): | Report | Answers | | - | - | | `sitemap_coverage` | Per bot: sitemap fetch cadence, pages discovered, publish-to-crawl lag | | `human` | Human sessions that arrived from an AI assistant, split by platform | | `correlation` | Per page, crawls versus the AI-referred sessions that followed | | `attribution` | Traffic by UTM tuple, referrer, and bot category | | `logs` | The raw request feed — filter by path, status, bots-only, host; cursor-paginated | `logs` is row-capped (25 default, 100 max) and **never returns `ip_address` or `user_agent`**: the IP is personal data with no analytical value once `country` is present, and the user-agent is superseded by the resolved bot identity. A new `host` parameter narrows any traffic report to specific hostnames on multi-domain projects. **New views on the existing families.** * `get_share_of_voice` gains `group_by="date"` — the daily brand-versus-competitor share series, for "is our share growing". * `get_topic_analysis` gains `view="prompts"` (per-prompt rank inside one topic, with the competitors outranking you — requires `topicId`) and `view="heatmap"` (average brand position per topic x AI platform). Both are also REST endpoints: `/topics/prompt-rankings` and `/topics/heatmap`. * `get_visibility` gains `include="industry_ranking"` — the competitive leaderboard attached to the summary. * Date-series views gain `include="annotations"`: the dated markers (campaign launches, migrations) overlapping the window, so a spike arrives with its explanation. **Filters.** `list_prompts` (and `GET /prompts`) accept `search` for a case-insensitive substring match on prompt text — `%` and `_` match literally rather than as wildcards. `topicId` accepts a comma-separated list on the surfaces whose queries take several (`get_visibility`, `get_share_of_voice`, `get_sentiment`, `get_topic_analysis`, `get_competitors`, `get_shopping_visibility`); single-topic surfaces (`list_prompts`, `get_query_fanout`, `get_ads`) document and accept one id rather than advertising a list they would reject. `list_projects` returns `dataAvailableFrom` per project — the earliest date with a **completed, scored** visibility report — so a client can size its windows instead of discovering the horizon through empty results. **Reading the new reports honestly.** Three contracts are worth knowing before you parse them: * `report="human"` carries a `summarySource`. Site-wide totals come from the first-party tracker while the AI figures fall back to GA4 when no tracker is installed; in that split state every `summary` field and `percentOfTotal` are `null` rather than `0`, and a failed GA4 read arrives as `aiReferral.sourceError` instead of as zeroes. * `report="correlation"` joins crawls and AI-referred sessions over the **same window** by path. That is co-occurrence, not attribution — nothing establishes that a session followed a crawl. Its `pagesConsidered` is a floor on the page count, with `populationTruncated` flagging when the upstream per-set cap means pages are missing. * `include="industry_ranking"` returns the top 25 entities, not the whole field: `hasMore` flags a longer leaderboard, `brandRankStatus` separates "ranks below the cutoff" from "unranked", and `totalEntities` is `null` when truncated. Arguments a view cannot apply are now `invalid_argument` errors rather than silent no-ops — an `include` on a view that attaches no blocks, `topicId` on the all-topics view, `platform` on the topic x platform heatmap, an explicit range longer than `group_by=prompt`'s 180-day ceiling, and half a log cursor. Impossible calendar dates (`2026-02-30`) are rejected instead of rolling into the next month, and markdown headings name an explicit range instead of relabelling it "last N days". `logs`, `sitemap_coverage`, and `attribution` require a crawler-log source and return `traffic_not_connected` for analytics-only projects, rather than an empty `200` that reads as "no crawler traffic". **August 2026** MCP family-grid consolidation — fewer tools, same answers **Four reads folded into their family's primary tool.** The visibility trend, the two sentiment side-views, and domain authority are now selector parameters instead of standalone tools: | Old tool | New call | | - | - | | `get_visibility_history` | `get_visibility` (`group_by: "date"`) | | `get_sentiment_history` | `get_sentiment` (`view: "history"`) | | `get_sentiment_competitors` | `get_sentiment` (`view: "competitors"`) | | `get_domain_authority` | `get_brand_profile` (`include: "domain_authority"`) | **The old names still work.** All four stay registered as deprecated stubs with byte-identical payloads, so existing scripts and scheduled jobs are unaffected today. They are scheduled for removal **after 2026-10-15** — the same \~60-day window the removed aliases got. An unsupported combination on the new views (for example `compare` on `view=history`, or a `platform` filter on `group_by=date`) returns `invalid_argument` instead of being silently ignored. **REST is untouched.** `GET /visibility/history`, `GET /sentiment/history`, `GET /sentiment/competitors`, and `GET /domain-authority` are not deprecated and follow the standard [12-month policy](/changelog#deprecation-policy) as before. **Leaner tool descriptions.** Tool descriptions across the server were tightened (\~18% smaller) with no behavioral facts removed — every MCP client loads the full tool list into model context each conversation, so this directly cuts per-conversation overhead. **August 2026** Period comparison, CSV export, per-URL rollup, MCP utility & docs tools **Period comparison in one call.** `GET /visibility`, `GET /visibility/topics`, `GET /share-of-voice`, and `GET /sentiment` (and the matching MCP tools `get_visibility`, `get_share_of_voice`, `get_sentiment`) accept `compare=previous_period` or `compare=previous_year`. The response gains an additive `comparison` block — the previous window's metrics plus current-minus-previous deltas — so "how did we do vs last month?" no longer needs two calls and client-side math. On `/visibility/topics`, per-topic deltas ride inline on each topic row. On `/share-of-voice`, every competitor row gains `previousSov`/`sovDelta`. **Per-URL metrics rollup.** New `GET /projects/{projectId}/pages/metrics?url=…` (and the `get_page_metrics` MCP tool): one page's three AI series joined in a single call — citations of the URL in AI answers, AI-referral sessions landing on it (by platform), and AI-crawler hits fetching it (by bot), each as daily buckets. Traffic series degrade to `available: false` when the LLM Analytics integration isn't connected; citations always answer. **CSV export + bigger pages.** The five biggest list endpoints — cited domains, cited URLs, prompts, issues, opportunities — accept `?format=csv` (RFC 4180, BOM-prefixed, spreadsheet-formula-hardened). Page-size caps rose for bulk pulls: cited domains/URLs and prompts to **1,000** rows per page, opportunities to 1,000; values above a cap clamp instead of erroring. **Consistent pagination everywhere.** `GET /issues`, `GET /opportunities`, and `GET /competitors` now return the full standard offset block (`hasMore` + `nextOffset` included) — the derive-it-yourself caveats are gone from the docs because they're gone from the API. **New MCP orientation tools.** `get_current_date` (UTC anchor + pre-computed 7/30/90/365-day window starts, so assistants never do date arithmetic), `whoami` (who this connection is, its read/write scopes, and its visible project roster), and `search_docs` / `read_doc` (full-text search + retrieval over this documentation, served from the app's embedded corpus — assistants can answer "how is the visibility score calculated?" in-chat). Plus two MCP **resources**: `searchable://glossary` and `searchable://glossary/full` — metric and concept definitions with the tools that read each one. **July 2026** New: responseMentionRate — one mention metric across endpoints **`responseMentionRate` is the mention metric to use.** It is the percentage (0-100) of AI responses that mention your brand, and it means **the same thing on every endpoint** — added to both `GET /visibility` (`summary.responseMentionRate`) and `GET /visibility/history` (per data point). `GET /visibility/history` data points also now return `responsesWithBrand`, so the rate is auditable from the payload alone. **Why:** `mentionRate` does not mean the same thing on both endpoints, and never has. * On `GET /visibility` it is a **share** of responses — bounded 0-100. * On `GET /visibility/history` it is a **density**: brand mentions ÷ responses × 100. Because one response can mention a brand several times, this routinely reads **above 100%** (a real project reads 393%). That is the value it has always returned, not a recent regression. `mentionRate` and `overallMentionRate` are therefore **deprecated but unchanged** on `GET /visibility`, `GET /visibility/history`, and `GET /visibility/prompts`. They keep returning exactly what they always have — we do not silently recalculate a shipped field. There is **no removal date**; per the policy below you would get at least 12 months' notice, and this entry starts no clock. If you want the density, it stays derivable as `brandMentions / totalResponses * 100`. **Migrating:** replace `mentionRate` with `responseMentionRate` on `/visibility` and `/visibility/history`. On `/visibility/prompts`, `mentionRate` / `overallMentionRate` are exact aliases of `visibilityScore` / `overallVisibilityScore` — same value, so switching is a rename with no data change. **Also new: `GET /visibility/platforms`** — per-AI-platform visibility (the platform counterpart to `/visibility/topics` and `/visibility/locations`). `platform` is comma-separated and accepts display names, aliases, and raw model ids. **July 2026** Client MCP & API revamp **MCP: sign in instead of pasting a key.** The MCP server now supports an OAuth 2.1 login flow (PKCE + Dynamic Client Registration) — connect Claude, Cursor, or another MCP client by signing in and approving access, scoped to exactly the projects and read/write permissions you choose. The `sea_` API-key bearer path still works unchanged for scripts and headless clients. See [MCP integration](/integrations/mcp). **New data coverage**, in REST and as MCP tools: * **Share of Voice & Competitors** — brand vs. competitor mention-share ranking, its daily trend, and the full tracked-competitor roster * **AI Traffic** — first-party AI-crawler visits and AI-referral sessions, sourced from the tracker/CDN pipeline * **Shopping visibility** — AI shopping/product-carousel appearances * **Prompt Answers** — raw, per-response AI-answer data (text, mentions, citations) for a single prompt * **`GET /reports`** — list previously generated shareable reports for a project **Write actions over MCP** — `generate_report`, `trigger_audit`, and `refresh_sitemap` are now callable as MCP tools (in addition to their existing REST POST endpoints), each gated on the `write` scope and an explicit confirmation. **Hardened REST responses.** Every response now carries an `X-Request-Id` and standard rate-limit headers (600 requests/minute per API key); errors are RFC 9457 `application/problem+json` with a stable `code` field you can branch on. See the new **[error reference](/api/errors)**. The `POST /reports`, `POST /audits`, and `POST /sitemap/refresh` endpoints additionally accept an `Idempotency-Key` header, so a retry after a timeout or dropped connection never triggers the action twice. **Clearer "not connected" errors.** Google Search Console, Google Analytics 4, and AI Traffic endpoints now return an actionable `409` with a direct link to the right settings page when the integration isn't connected yet, instead of a generic failure. **This page, and the interactive [API reference](/api-reference/introduction)** — a full OpenAPI 3.1 spec with a live playground for every endpoint, generated straight from the shipped route contracts. ## Deprecation policy * **12 months' notice.** We give at least 12 months' notice before removing or changing the behavior of any documented endpoint, parameter, or response field. Adding a new endpoint or an additive response field is not a breaking change and isn't subject to this notice. * **`Deprecation` / `Sunset` headers.** Once an endpoint is scheduled for removal, its responses carry a `Deprecation` header (the date it was deprecated) and, once a removal date is fixed, a `Sunset` header (the date it stops working), per the [IETF Sunset header convention](https://www.rfc-editor.org/rfc/rfc8594). Nothing on this API is scheduled for removal today, so no `Sunset` header is in play. The `mentionRate` / `overallMentionRate` response fields are marked deprecated in the API reference (see the July 2026 entry above) — they remain fully supported with no removal date, and are superseded by `responseMentionRate`. * **Versioning posture.** `/api/mcp/*` — despite the name — is the frozen v1 REST contract: stable, versionless-by-default. New capabilities land as new endpoints or new additive fields, never as a breaking change to an existing one. `/api/v1/*` is reserved for surfaces that need independent versioning from that contract (currently just `/api/v1/projects/{projectId}/audits`, which mirrors `GET /api/mcp/projects/{projectId}/audits` exactly). * A breaking change, if one is ever required, ships as a new endpoint or a new version prefix, announced here first, with the prior version supported through the full notice window. Questions about an upcoming change? **[support@searchable.com](mailto:support@searchable.com)**. # Connecting Your First Domain Source: https://docs.searchable.com/getting-started/creating-your-project Step-by-step guide to setting up your first domain in Searchable ## What is a Domain in Searchable? In Searchable, a **domain** represents a website you want to monitor and optimize for AI visibility. Each domain can track your pages, run audits, monitor AI mentions, and generate optimized content. > [!TIP] > > In the app, each domain is called a **project**. You'll see "Projects" in the project dropdown and in your settings. Most users set up **one project per website** they manage. ## Your First Domain Your first domain is created for you during onboarding, so there's nothing extra to do. If you haven't signed up yet, start with the [Quickstart](/quickstart). ## Before You Start To add another domain, make sure you have: > [!TIP] > > Your website's domain (e.g.`yourwebsite.com`) > [!TIP] > > A Professional plan or higher, and an Owner, Super Admin, or Admin role in your workspace > [!TIP] > > Your brand name, and any other names or spellings it goes by > [!TIP] > > Your brand's reach and primary location > [!TIP] > > 3 of your main competitors (you can add up to 5) ## Adding Another Domain **1. Open the project dropdown** Click your current project's name at the top of the sidebar, then click **New Project** at the bottom of the dropdown. **2. Enter your domain** Choose the workspace you want to add it to, then enter the website you want to track. > [!TIP] > > You can include a path, like `example.com/uk`, if you only want to track part of a site. **3. Name your brand** This is how we'll refer to your brand in prompts and reports. If your brand goes by other names or spellings, click **My brand has other names or spellings** to add them, so those mentions are counted too. **4. Add your brand details** Tell us about your brand so we can tailor your prompts, competitors, and content: **Brand reach** * **Global**: customers in multiple countries, with no single primary market * **Primary market + international reach**: a strong home market, but you also ship or serve globally * **Nationwide**: you operate across an entire country * **Regional**: a specific region, state, or set of provinces * **Local**: a single city, neighbourhood, or local area **Primary location** and **Language**: where your main market is, and the language your customers use. **5. Choose your competitors** Pick from our suggestions or add your own. List your 3 main competitors, up to 5 in total. > [!TIP] > > **Generate prompts after creating** is on by default. If you turn it off, prompt generation is skipped and you can run it manually later. **6. Review your prompts** We'll analyze your website and create a set of visibility prompts. This is your baseline visibility test. You can edit or deselect any prompt, and switch on **Show Topics** to see them grouped by topic. Click **Confirm** when you're happy, or **Skip** to review them later. > [!NOTE] > > How many domains you can track depends on your plan. See [Plans & Pricing](/getting-started/plans-pricing) for details. ## After Adding a Domain Once your domain is set up, here's what you'll see: ### View Initial Results > **Pages Discovered** > > The pages Searchable found in your sitemap, listed in On Page → Site Health > **AI Visibility Baseline** > > Your starting point for tracking brand mentions, ready once your first visibility report completes > **Site Health Score** > > Your overall Technical and AEO score, available once you've run your first audit > **Critical Issues** > > Urgent problems found by your audits, listed in On Page → Issues ### Next Actions 1. **Review Discovered Pages** * Go to **On Page** → **Site Health** * Add any missing pages manually * Run an audit on your most important pages 2. **Set Up LLM Analytics** * Go to **LLM Analytics** → **Setup**, confirm your domain, and connect a data source (such as Cloudflare, Vercel, or WordPress) * LLM Analytics stays empty until a source is connected 3. **Set Up Your Knowledge Base** * Check your competitors and brand names are correct * Add context about your brand for the Searchable Agent 4. **Fix Critical Issues** * Go to **On Page** → **Issues** * Start with critical and high-priority items 5. **Add More Prompts** * Go to **Prompts** → **Your Prompts** * Add the questions your customers actually ask 6. **Set Up Integrations** * Connect Google Search Console for keyword data * Add Google Analytics for traffic insights ## Managing Your Domains ### Switching Between Domains * Click your current project's name at the top of the sidebar to open the project dropdown * Search for a project, or pick one from the list * Click **View all projects** to see everything at once ### Renaming or Deleting a Domain Open **All settings** → **Projects**. Owners, Super Admins, and Admins can rename or delete projects here. > [!WARNING] > > Deleting a project permanently removes its data, including its visibility history. This can't be undone. ### Comparing Domains Click **View all projects** in the project dropdown. The All Projects page shows your average visibility score, a trend chart comparing up to five projects, and a card for each project. Click any card to jump into that project. ### Team Access * Invite team members in **All settings** → **Team** * Choose a role and which projects each person can access * Inviting team members requires a Professional plan or higher. Seats vary by plan, see [Plans & Pricing](/getting-started/plans-pricing) > [!TIP] > > If you don't select any projects when sending an invite, that person gets access to every project in your workspace. Pick specific projects to limit their access. ### Notifications Turn on the weekly **New opportunities** email in **All settings** → **Notifications** to get a summary of new recommendations for your projects. ## Common Issues & Solutions > **My pages aren't showing up** > > Searchable finds your pages using your sitemap. > > **Solutions:** > > * Make sure your sitemap is up to date and publicly accessible > * Check that your robots.txt allows crawling. Pages that block crawlers are flagged as an issue, since AI engines would be blocked the same way > **Some pages weren't discovered** > > **Possible Causes:** > > * Pages are missing from your sitemap > * Pages were added to your sitemap after Searchable last synced it > > **Solutions:** > > * Add missing pages to your sitemap, then re-sync it > * Add them manually in **On Page** → **Site Health** > **I entered the wrong domain** > > A project's domain can't be changed once it's created. Delete the project in **All settings** → **Projects**, then add it again with the correct domain using **New Project**. Deleting a project permanently removes its data. > **I can't add another domain** > > Adding projects requires a Professional plan or higher and an Owner, Super Admin, or Admin role. If you've reached your plan's project limit, you'll see an option to upgrade. > **What if I need more pages/prompts/articles?** > > Your limits depend on your plan. Check your current usage in **All settings** → **Usage**, and see [Plans & Pricing](/getting-started/plans-pricing) for upgrade options. > > For custom limits, contact [support@searchable.com](mailto:support@searchable.com). ## Best Practices > [!TIP] > > Audit your most important pages first > [!TIP] > > Add prompts that your target customers would actually ask > [!TIP] > > Turn on the weekly opportunities email to stay informed > [!TIP] > > Review your dashboard weekly to track progress > [!TIP] > > Connect integrations early for richer data ## Next Steps > **[Understanding Dashboard](/getting-started/understanding-dashboard)** > > Learn to navigate your domain dashboard > **[Understanding Scores](/using-searchable/understanding-scores)** > > Understand what your scores mean > **[Working with Prompts](/using-searchable/working-with-prompts)** > > Create effective prompts for tracking > **[Set Up Integrations](/integrations/overview)** > > Connect your tools and platforms # Plans & Pricing Source: https://docs.searchable.com/getting-started/plans-pricing Find the right plan and see the limits on your account ## Plans Searchable plans scale by the number of projects, prompts, page audits, articles, and team seats you need, as well as which AI engines you track. Exact limits depend on your plan and contract. If you reach a limit, we'll show you an option to upgrade. > [!NOTE] > > See the [pricing page](https://searchable.com/pricing) for current plans. For your full usage and limits, open **All settings** → **Usage**. This tab is available to workspace owners and admins. ## Custom Plans Custom plans are available for larger domain portfolios, higher audit volume, and tailored onboarding. [Contact our team](mailto:support@searchable.com) when your workspace needs limits beyond your current plan. # Referral Program Source: https://docs.searchable.com/getting-started/referral-program Give someone 30% off Searchable and earn 20% of what they pay for six months ## How it works **1. Get your link** Click **Invite friends** in the sidebar, or go to [app.searchable.com/referral](https://app.searchable.com/referral). You can also search "referral" in the command palette (**⌘K**). Your unique link, stats, and payout settings all live on this page. **2. Share it** Send your link to people and businesses who don't use Searchable yet. See [Where you can share your link](#where-you-can-share-your-link) for what's allowed. **3. They sign up and subscribe** Your friend creates a new Searchable account within 30 days of clicking your link, then starts a paid plan. Their discount is applied automatically at checkout. **4. You get paid** Once their first payment has cleared a 30-day holding period, your reward becomes payable. Payouts go out monthly. ## Rewards | | You | Your friend | | - | - | - | | **Reward** | 20% of what they pay | 30% off their subscription | | **How long** | First 6 months from their first payment | First 6 months | | **Limit** | No cap on referrals | New customers only | Your 20% is calculated on what your friend actually pays, after their discount, refunds, and taxes. For example, if your friend picks the Pro plan at \$125/month, they pay \$87.50/month and you earn \$17.50/month, or \$105 over six months. After six months, your friend's subscription moves to the standard price. > [!NOTE] > > The referral discount can't be combined with other promotions or custom pricing. Existing customers can't apply it to a current or past subscription. ## Who can take part **To refer someone, you need to:** * Have an active, paid Searchable subscription (trials don't qualify) * Be 18 or older * Live in a country supported by Dub, our payout partner **Your friend needs to:** * Be new to Searchable. People who already have an account, have had a trial, or are already in touch with our team don't qualify. * Sign up through your link and start a paid plan * Stay subscribed for at least 30 days after their first payment You can't refer yourself, your own company, or accounts you control. ## Getting paid Rewards are tracked and paid through **Dub**, which powers the referral page in Searchable. Connect a payout method from the referral page: * **Bank transfer** via Stripe Express, paid in your local currency * **PayPal** * **USDC** to a crypto wallet (a transaction fee may apply) Rewards are calculated in USD and paid automatically once your balance reaches **\$20**. Smaller balances roll over to the next month. > [!WARNING] > > If your friend gets a refund, cancels, or charges back during the 30-day holding period, the reward for that referral is removed from your balance. You're responsible for any taxes on your rewards. Dub may ask for tax forms (such as a W-9 or W-8) when you set up payouts. ## Where you can share your link **Allowed:** * Your own website or blog * Videos, podcasts, newsletters, and other content you create * Organic social media posts * Emails and direct messages to people who've opted in to hear from you **Not allowed:** * Paid ads of any kind, including bidding on "Searchable" as a keyword * Coupon, deal, or cashback sites * Spam, bulk unsolicited messages, or automated outreach * Shortening or masking your link in a way that hides where it goes If you share your link publicly, say that you may earn a reward. Something like: *"I may earn a reward if you sign up for Searchable using my link."* ## Referral program vs. affiliate program The referral program is built for Searchable customers recommending Searchable to people they know. If you're a consultant, creator, or agency who wants to promote Searchable to an audience or client base, our [affiliate program](https://www.searchable.com/affiliates) is the better fit. ## FAQ > **My friend signed up but isn't showing in my referrals** > > Referrals are tracked with a cookie when your friend clicks your link. A referral may not be recorded if: > > * They created their account more than 30 days after clicking your link > * They clicked someone else's referral link after yours (the last link clicked gets the credit) > * They signed up without clicking your link first, or in a different browser > * An ad blocker or privacy setting blocked tracking > * They already had a Searchable account, trial, or conversation with our team > **My friend is on a trial. When does the referral count?** > > When they start a paid plan. Trials don't earn a reward on their own. Your reward becomes payable 30 days after their first payment. > **When will I get paid?** > > Each reward is held for 30 days after your friend's payment, then paid in the next monthly payout once your balance is \$20 or more. > **What happens if my friend cancels?** > > You earn rewards for each month they pay, up to six months. If they cancel, rewards stop. If they cancel or get a refund within the first 30 days, that reward is reversed. > **Do I keep earning if I cancel my own subscription?** > > Yes. You keep earning for each month your friend stays subscribed, up to six months from their first payment. > **I changed the email on my Searchable account** > > Your referral account doesn't update automatically. [Contact support](mailto:support@searchable.com) so we can link it to your new email. > **Where can I read the full terms?** > > The program rules are on this page. Your referral account and payouts run through Dub's [Partner Terms of Service](https://dub.co/legal/partners). # Understanding Your Dashboard Source: https://docs.searchable.com/getting-started/understanding-dashboard Navigate your Searchable dashboard and complete the essential setup steps ## Where You'll Land After you sign in, you'll land on the **Searchable Agent**. At the top of your sidebar, you can switch between two modes: ![Searchable Dashboard Overview](https://mintcdn.com/searchable/Zyms3YY9H2Yf2QTr/searchable-dashboard.png?fit=max\&auto=format\&n=Zyms3YY9H2Yf2QTr\&q=85\&s=7eb194e79577bdb1dc2b323ce2c03367) > **Dashboard** > > All your analytics, prompts, content, and on-page tools > **Agent** > > Chat with the Searchable Agent about your data, and manage your Skills ## Getting Started Checklist Open **Getting Started** from the progress card in your sidebar. It's a short checklist that walks you through the essentials and tracks your progress automatically. **1. Watch a quick overview** A short video introduction to Searchable **2. Review your visibility scores** See how your brand shows up in AI answers. Learn more in [AI Visibility Tracking](/using-searchable/visibility-tracking). **3. Build your Knowledge Base** Tell Searchable about your brand, competitors, and writing style. See [Set up your Knowledge Base](/quickstart#set-up-your-knowledge-base). **4. Ask the AI agent** Ask the Searchable Agent a question about your data. Learn more in [Searchable Agent](/advanced/ai-assistant). **5. Connect Google Search Console** Add your search data for richer insights. See [Google Search Console Integration](/integrations/google-search-console). **6. Invite team members** Bring your team into your workspace. See [Teams & Collaboration](/advanced/teams-collaboration). > [!TIP] > > The progress card disappears from your sidebar once you've completed onboarding. For a full walkthrough of each step, see the [Quickstart](/quickstart). ## Finding Your Way Around ### Project Dropdown Click your current project's name at the top of the sidebar to switch projects, search for a project, create a **New Project**, or click **View all projects** to see them all at once. Learn more in [Connecting Your First Domain](/getting-started/creating-your-project). ### Analytics > **[AEO Insights](/using-searchable/visibility-tracking)** > > How your brand shows up in AI answers: visibility, mentions and citations, sentiment, sources, and topics > **Shopping** > > Which products and brands appear when AI engines show shopping results for your prompts > **[LLM Analytics](/setup/overview)** > > Which AI crawlers visit your site, and the visitors AI sends you > **Traffic** > > Your website traffic and search performance from Google Search Console, Google Analytics, and Bing > **Reports** > > Shareable snapshot reports built from your dashboard data ### Prompts > **[Your Prompts](/using-searchable/working-with-prompts)** > > Every prompt you're tracking, with its visibility, position, and sentiment > **Prompt Research** > > Discover new prompts and topics, and review suggestions before you track them ### Actions > **[Content](/using-searchable/content-generation)** > > Create and optimize content with the AI content writer > **Opportunities** > > A prioritized list of recommended actions, updated nightly. The number shows how many are waiting for you. ### On Page > **[Site Health](/using-searchable/site-audits)** > > The technical and AEO health of your website's pages, and where you run audits > **Issues** > > Every problem found across your site, grouped by type and ranked by severity ### Knowledge Base Open **Knowledge Base** from the bottom of your sidebar to manage your brand profile, competitors, topics, writing style, and more. See [Set up your Knowledge Base](/quickstart#set-up-your-knowledge-base). ### Agent Mode Switch to **Agent** at the top of your sidebar to start a **New Chat** with the Searchable Agent, or open **Skills** to create reusable instructions the Agent can follow. ### Top Bar At the top of every page, you'll find: * **Find anything**: search for any page or setting. You can also press **⌘K** (Mac) or **Ctrl+K** (Windows) from anywhere * **Learn**: opens the Learning Center, with guides to help you get the most out of Searchable * **Export**: takes you to **Reports**, where you can create a shareable report from your data ### Settings Open the user menu at the bottom of your sidebar and select **All settings**. From here you can manage your profile, notifications, projects, team, integrations, and usage. The tabs you see depend on your role. > [!NOTE] > > Some sections depend on your plan. For example, Shopping appears once shopping data has been captured for your prompts, and both Shopping and Reports require a Professional plan or higher. ## Your Weekly Routine Once you're set up, a short weekly check-in keeps you on top of your AI visibility: **1. Check your visibility** Open **AEO Insights** to see how your visibility has moved across AI engines and how you compare to competitors **2. Review Opportunities** Work through your recommended actions, marking each one as done or declining it **3. Look for content gaps** Find prompts where your brand isn't mentioned, and plan content to target them **4. Fix on-page issues** Start with critical issues in **Issues**, then re-audit the affected pages to confirm your fixes **5. Share your progress** Create a report in **Reports** to share results with your team or clients > [!TIP] > > Don't obsess over daily fluctuations - focus on weekly and monthly trends. ## Common Questions > **I completed the checklist, now what?** > > Excellent! Now you can move to the weekly routine above. The checklist gets you started; the weekly routine builds long-term AI visibility. > **Which area should I focus on first?** > > Start with the **Getting Started** checklist, then **AEO Insights** to understand your visibility baseline. After that, most of your time will go into **Opportunities**, **Content**, and fixing **Issues**. > **How often does data update?** > > * **AI visibility**: your prompts run every day (some custom plans run weekly) > * **Opportunities**: generated nightly > * **Site audits**: whenever you run them > * **Search Console data**: runs about 3 days behind, matching Google's own reporting delay > **Why are some metrics missing or showing dashes?** > > Some metrics require integrations or data to be collected first: > > **Missing traffic data:** Connect Google Analytics 4 in All settings → Integrations > > **Missing keyword data:** Connect Google Search Console in All settings → Integrations > > **Missing Site Health scores:** Run an audit in On Page → Site Health > > **Empty LLM Analytics:** Go to LLM Analytics → Setup, confirm your domain, and connect a data source. LLM Analytics stays empty until a source is connected, and AI crawler activity needs a crawler-log source such as Cloudflare or Vercel. See [LLM Analytics Setup](/setup/overview) > > **Zero AI mentions:** Normal for new brands. Working through your Opportunities and creating content can help improve this over time > **Why can't I see a section mentioned here?** > > Some sections depend on your plan, your role, or your data. For example, Shopping only appears once shopping data has been captured for your prompts. If you think you should have access to something, check with your workspace owner or admin. > **Can I share my dashboard with team members?** > > Yes! Invite team members in **All settings** → **Team**. Inviting team members requires a Professional plan or higher, and seats vary by plan. See [Plans & Pricing](/getting-started/plans-pricing). > > **Roles available:** > > * **Owner**: Full access, including billing > * **Super Admin**: Full access, including billing, except deleting or transferring the workspace > * **Admin**: Manages the team and their assigned projects > * **Member**: Works in their assigned projects > * **Viewer**: Read-only access ## Dashboard Performance If your dashboard loads slowly: 1. **Reduce Date Range**: View last 30 days instead of 12 months 2. **Clear Filters**: Reset any active filters on tables 3. **Check Connection**: Ensure stable internet connection 4. **Contact Support**: Reach out to [support@searchable.com](mailto:support@searchable.com) if issues persist ## Next Steps > **[Understanding Scores](/using-searchable/understanding-scores)** > > Deep dive into what each score means and how to improve them > **[Working with Prompts](/using-searchable/working-with-prompts)** > > Master advanced prompt strategies and templates > **[Site Audits](/using-searchable/site-audits)** > > Comprehensive guide to running and interpreting audits > **[Content Generation](/using-searchable/content-generation)** > > Advanced content creation and optimization techniques > **[Set Up Integrations](/integrations/overview)** > > Connect Google Search Console, GA4, and your CMS > **[Plans & Pricing](/getting-started/plans-pricing)** > > Upgrade for higher limits and advanced features # Google Analytics 4 Integration Source: https://docs.searchable.com/integrations/google-analytics-4 Connect GA4 to track traffic, conversions, and user behavior from AI platforms ## Overview Integrate Google Analytics 4 with Searchable to understand how AI visibility translates to website traffic and conversions. Track user journeys from AI platforms to your site. > [!NOTE] > > GA4 integration is available on Professional, Agency, and Custom plans. ## Benefits > **Traffic Attribution** > > See which AI platforms drive visitors > **Conversion Tracking** > > Measure ROI of AI visibility efforts > **User Behavior** > > Understand how AI-referred users interact > **Revenue Impact** > > Calculate financial value of AI mentions ### What Data Gets Synced * **Traffic Sources**: Where visitors come from (including AI platforms) * **User Behavior**: Pages viewed, time on site, bounce rate * **Conversions**: Goal completions and e-commerce transactions * **Demographics**: Age, gender, interests (aggregated) * **Technology**: Device, browser, OS * **Events**: Custom events and interactions * **Engagement**: Session duration, scroll depth, clicks ## Prerequisites > [!TIP] > > Have Google Analytics 4 property set up for your website > [!TIP] > > Editor or Administrator access to the GA4 property > [!TIP] > > At least 7 days of traffic data in GA4 > [!TIP] > > Searchable Professional, Agency, or Custom plan > [!WARNING] > > Viewer access in GA4 is insufficient. You need Editor or Administrator permissions to connect. ## Setup Guide **1. Navigate to Integrations** In Searchable: 1. Go to **Settings** → **Integrations** 2. Find **Google Analytics 4** 3. Click **Connect** **2. Authenticate with Google** 1. Select your Google account 2. Review permissions 3. Click **Allow** **Permissions needed:** * View Analytics data * Edit Analytics configuration (for enhanced tracking) **3. Select GA4 Property** Choose your property: * Select the correct GA4 property (not Universal Analytics) * Multiple properties? Choose your main website * Can connect additional properties later **4. Configure Enhanced Tracking (Optional)** Enable advanced features: **AI Referrer Tracking:** * Automatically tags traffic from AI platforms * Creates custom dimensions for AI sources * Tracks specific AI models (ChatGPT, Claude, etc.) **Event Tracking:** * Tracks AI-related events * Content engagement from AI visitors * Conversion attribution > [!TIP] > > Recommended: Enable enhanced tracking for complete insights. **5. Run Initial Sync** * Click **"Start Sync"** * First sync takes 1-3 minutes * Historical data import (last 30 days) * Email notification when complete **6. Verify Connection** Check that data is flowing: 1. Go to **Analytics** tab 2. See GA4 widgets and charts 3. Verify traffic numbers match GA4 4. Check "Last Synced" timestamp ## AI Traffic Attribution ### How Searchable Tracks AI Referrers When enhanced tracking is enabled: **UTM Parameters:** ```text theme={null} ?utm_source=chatgpt&utm_medium=ai&utm_campaign=searchable_tracking ``` **Custom Dimensions:** * `ai_platform`: Which AI model (ChatGPT, Claude, Gemini, etc.) * `ai_prompt`: Which tracked prompt drove the visit (hashed) * `ai_session`: Session identifier from AI platform **Referrer Detection:** * Direct traffic analysis * URL pattern matching * Custom JavaScript tracking * Server-side attribution ### Viewing AI Traffic In Searchable Analytics: ![AI Traffic Dashboard](https://mintcdn.com/searchable/Zyms3YY9H2Yf2QTr/images/ga4-ai-traffic-placeholder.png?fit=max\&auto=format\&n=Zyms3YY9H2Yf2QTr\&q=85\&s=2b79d6790b1e6f78d1342ef38fed5b20) **Metrics Available:** * Sessions from each AI platform * Page views per AI source * Average session duration * Bounce rate by AI platform * Conversions attributed to AI * Revenue from AI-referred traffic ## Conversion Tracking ### Setting Up Goals Define what counts as a conversion: **E-commerce:** * Purchases * Add to cart * Checkout initiated * Revenue tracking **Lead Generation:** * Form submissions * Email signups * Demo requests * Contact form fills **Engagement:** * Time on site > X minutes * Pages per session > X * Scroll depth > X% * Video plays ### AI Conversion Attribution See which prompts drive conversions: 1. **Direct Attribution**: User converts in same session 2. **Assisted Conversions**: AI visit assists later conversion 3. **Multi-Touch**: AI touchpoint in longer journey **Example Report:** ```text theme={null} Prompt: "What are the best CRM tools for startups?" - Sessions: 145 - Conversions: 12 - Conversion Rate: 8.3% - Revenue: $4,800 - ROI: $33 per mention ``` ## User Behavior Analysis ### AI Visitor Behavior Compare AI-referred vs. other visitors: | Metric | AI-Referred | Organic Search | Direct | | - | - | - | - | | Avg Session Duration | 4:32 | 2:15 | 3:05 | | Pages / Session | 4.2 | 2.3 | 3.1 | | Bounce Rate | 32% | 58% | 45% | | Conversion Rate | 5.2% | 2.8% | 3.9% | **Insights:** * AI-referred visitors often spend more time * Higher engagement and conversion rates * Different page paths and interests ### Content Performance See which content attracts AI-referred visitors: * Most viewed pages * Entry pages from AI * Exit pages * Content engagement * Internal navigation patterns ## Dashboard Widgets ### Traffic Overview * Total visitors * AI vs. non-AI breakdown * Traffic trends over time * Platform distribution ### Conversion Funnel * Visitors → Engaged → Converted * Drop-off points * Conversion rates by source * Revenue attribution ### Real-Time * Active users now * Traffic sources (live) * Popular pages * Recent conversions ### Audience Insights * Demographics * Interests * Technology * Geographic distribution ## Data Refresh | Plan | Sync Frequency | Historical Data | Delay | | - | - | - | - | | Professional | Hourly | 90 days | 24 hours\* | | Agency | Real-time | Unlimited | Real-time\* | | Custom | Real-time | Custom | SLA-based | \*Google Analytics 4 has inherent data processing delays of 24-48 hours ## Troubleshooting > **Connection Failed** > > **Common Issues:** > > * Insufficient GA4 permissions > * Wrong property type (UA vs. GA4) > * Account access issues > > **Solutions:** > > * Verify Editor/Admin access in GA4 > * Ensure using GA4 (not Universal Analytics) > * Try different Google account > * Check GA4 property is active > **No Traffic Data** > > **Possible Causes:** > > * Brand new property (no data yet) > * Wrong property selected > * GA4 not receiving traffic > * Filters excluding data > > **Check:** > > * Property has data in GA4 directly > * Selected correct property > * GA4 tracking code is installed > * Wait 24-48 hours for data processing > **AI Traffic Not Showing** > > **Why This Happens:** > > * Enhanced tracking not enabled > * AI visitors use VPNs/privacy tools > * Insufficient sample size > * Attribution window too short > > **Solutions:** > > * Enable enhanced tracking in integration settings > * Wait for more data (needs 100+ visitors) > * Adjust attribution window > * Verify tracking parameters are working ## Privacy & Compliance **Data Handling:** * Only aggregated data is stored * No personally identifiable information * GDPR and CCPA compliant * User privacy respected **Cookie Consent:** * Searchable respects your existing consent setup * No additional cookies required * Works with consent management platforms **Data Retention:** * Pro: 90 days * Agency: Unlimited retention * Custom: Custom retention windows * Can be deleted anytime ## Advanced Features (Custom) ### Custom Reports Build custom dashboards: * Drag-and-drop metrics * Custom dimensions * Calculated fields * Scheduled exports ### Predictive Analytics AI-powered predictions: * Conversion probability * Churn likelihood * Lifetime value * Trend forecasting ### Cross-Domain Tracking Track users across multiple properties: * Main site + subdomain * Multiple brand websites * App + web tracking ### Data Studio Integration Export to Google Data Studio: * Pre-built templates * Custom visualizations * Automated reporting * Stakeholder dashboards ## Best Practices > [!TIP] > > Enable enhanced tracking for AI attribution > [!TIP] > > Set up conversion goals from day one > [!TIP] > > Monitor AI traffic weekly > [!TIP] > > Compare AI vs. organic visitor behavior > [!TIP] > > Use insights to optimize prompt strategy > [!TIP] > > Track ROI of AI visibility efforts ## Use Cases ### Calculate AI ROI ```text theme={null} Monthly AI Mentions: 500 AI-Referred Sessions: 250 Conversions: 20 Conversion Value: $100 Total Revenue: $2,000 Cost of Searchable Pro: $49 ROI: 4,000% ($2,000 / $49) ``` ### Optimize Content 1. Identify pages with high AI traffic 2. See which ones convert best 3. Create similar content 4. Optimize low-converting pages ### Improve Prompts 1. Track which prompts drive most traffic 2. Measure conversion rates per prompt 3. Double down on high-converters 4. Refine or remove low-performers ## Disconnecting GA4 To disconnect: 1. Settings → Integrations 2. Click "Disconnect" on GA4 3. Confirm disconnection **What Happens:** * No new data synced * Historical data preserved (30 days) * Can reconnect anytime * No impact on your GA4 property ## Next Steps > **[Google Search Console](/integrations/google-search-console)** > > Add GSC for complete SEO data > **[Content Optimization](/using-searchable/content-generation)** > > Turn insights into better content > **[Score Benchmarks](/using-searchable/understanding-scores)** > > Master the analytics dashboard > **[Visibility Tracking](/using-searchable/visibility-tracking)** > > Measure AI-driven performance # Google Search Console Integration Source: https://docs.searchable.com/integrations/google-search-console Connect Google Search Console to enrich your SEO and AEO data ## Overview Connecting Google Search Console (GSC) to Searchable unlocks powerful insights by combining traditional SEO data with AI visibility metrics. See which keywords drive both Google rankings and AI mentions. > [!NOTE] > > GSC integration is available on Professional, Agency, and Custom plans. ## Benefits > **Keyword Insights** > > See which keywords perform in both Google and AI platforms > **Traffic Correlation** > > Understand how SEO and AEO work together > **Technical Issues** > > Identify crawl errors and indexing problems > **Performance Tracking** > > Monitor impressions, clicks, CTR, and position ### What Data Gets Synced * **Keywords**: Queries, impressions, clicks, CTR, average position * **Pages**: Top performing pages and their metrics * **Countries**: Geographic performance data * **Devices**: Desktop, mobile, tablet breakdown * **Search Appearance**: Rich results, AMP, etc. * **Coverage**: Indexing status and errors * **Core Web Vitals**: Performance metrics * **Mobile Usability**: Mobile-specific issues ## Prerequisites Before connecting GSC to Searchable: > [!TIP] > > Have a verified Google Search Console property for your domain > [!TIP] > > Owner or Full User permission in GSC (not just Restricted access) > [!TIP] > > At least 28 days of data in GSC (for meaningful analysis) > [!TIP] > > Searchable Professional, Agency, or Custom plan > [!WARNING] > > Restricted Users in GSC cannot connect integrations. You need Owner or Full User access. ## Setup Guide **1. Navigate to Integrations** In your Searchable project: 1. Go to **Settings** 2. Click **Integrations** 3. Find **Google Search Console** 4. Click **Connect** **2. Authenticate with Google** A popup window opens: 1. Select your Google account 2. Review permissions requested 3. Click **Allow** **Permissions needed:** * View Search Console data * View and manage your properties > [!NOTE] > > Searchable only reads data - we never modify your GSC settings or data. **3. Select Property** Choose the GSC property to connect: **Property Types:** * **Domain property**: Recommended (covers all subdomains and protocols) * **URL prefix**: Only covers specific protocol and subdomain > [!TIP] > > If you have both types, choose the Domain property for complete coverage. **4. Configure Sync Settings** Set your preferences: **Sync Frequency:** * Hourly (recommended for most users) * Daily (for large sites to reduce API usage) * Manual (sync on-demand only) **Data Range:** * Last 7 days * Last 28 days (recommended) * Last 90 days (maximum) **Filters:** * Include/exclude specific queries * Country filters * Device type filters **5. Run Initial Sync** Click **"Start Initial Sync"** * First sync takes 2-5 minutes * You'll receive an email when complete * Data appears in Analytics tab > [!NOTE] > > Initial sync imports the full date range. Subsequent syncs only import new data. **6. Verify Connection** Confirm everything is working: 1. Go to **Analytics** tab 2. Look for GSC data widgets 3. Check "Last Synced" timestamp 4. Verify data looks accurate ## Using GSC Data in Searchable ### Keyword Analysis ![GSC Keywords in Searchable](https://mintcdn.com/searchable/Zyms3YY9H2Yf2QTr/images/gsc-keywords-placeholder.png?fit=max\&auto=format\&n=Zyms3YY9H2Yf2QTr\&q=85\&s=99238d7516b289f8f1dd5211e6416a15) **Combined View:** * See which keywords rank in Google AND get AI mentions * Identify high-potential keywords with good SEO but no AI presence * Find keywords where AI visibility exceeds Google rankings **Optimization Opportunities:** * Keywords with high impressions but low AI visibility * Keywords ranking well in AI but not in Google * High-CTR keywords to target with content ### Page Performance **Metrics per Page:** * Google impressions and clicks * AI mention frequency * Combined visibility score * Traffic sources breakdown **Insights:** * Which pages drive most organic traffic * Which pages get AI citations * Content gaps and opportunities * Pages needing optimization ### Search Analytics **Filters and Segmentation:** * By date range * By device (mobile, desktop, tablet) * By country * By search appearance * By query type **Exports:** * CSV download of all data * Custom reports * Scheduled email reports ## Data Refresh Schedule | Plan | Sync Frequency | Historical Data | Real-time Updates | | - | - | - | - | | Professional | Hourly | 90 days | Yes | | Agency | Real-time | Unlimited | Yes | | Custom | Real-time | Custom | Yes + API | > [!NOTE] > > Google Search Console data has a 2-3 day delay from Google. This is a GSC limitation, not Searchable. ## Understanding the Combined Dashboard ### SEO + AEO Correlation **High SEO, High AEO** (Top Right Quadrant) * Your best performing content * Maintain and expand on these topics * Use as templates for new content **High SEO, Low AEO** (Top Left Quadrant) * Good traditional SEO but AI models don't cite you * Opportunity: Optimize for AI visibility * Add structured data, improve answer formats **Low SEO, High AEO** (Bottom Right Quadrant) * AI models cite you but Google rank is low * Opportunity: Improve traditional SEO * Build backlinks, optimize technical SEO **Low SEO, Low AEO** (Bottom Left Quadrant) * Content needs comprehensive optimization * Consider rewriting or consolidating * May not be worth the effort vs. creating new content ### Traffic Attribution See how traffic sources contribute: ```text Example: Traffic Breakdown theme={null} Total Organic Traffic: 10,000 visitors/month From Google Search: 8,500 (85%) From AI Platforms: 1,200 (12%) From Other Search: 300 (3%) AI-Referred Conversion Rate: 4.2% Google-Referred Conversion Rate: 2.8% ``` ## Troubleshooting > **Connection Failed** > > **Common Causes:** > > * Insufficient GSC permissions > * Account not verified in GSC > * Using personal vs. organizational account > > **Solutions:** > > * Verify you have Owner or Full User access > * Check property is verified in GSC > * Try using a different Google account > * Clear browser cookies and retry > **No Data Showing** > > **Possible Reasons:** > > * Recently created property (needs 28 days of data) > * Wrong property selected > * Filters excluding all data > * Sync hasn't completed yet > > **Check:** > > * Property has data in GSC directly > * Selected correct property in Searchable > * Sync status shows "Connected" > * Last sync timestamp is recent > **Sync Errors** > > **Error Messages:** > > * "Rate limit exceeded": Wait 1 hour, then retry > * "Property not found": Re-verify property ownership in GSC > * "Authentication expired": Disconnect and reconnect > * "Insufficient permissions": Check GSC access level > > **Solution:** > Most sync errors resolve by: > > 1. Disconnecting integration > 2. Clearing browser cache > 3. Reconnecting with fresh authentication > **Data Doesn't Match GSC** > > **Why Data May Differ:** > > * Time zone differences > * Date range selection > * Filters applied in Searchable > * GSC data still processing > * Different aggregation methods > > **What to Do:** > > * Verify same date range in both platforms > * Check timezone settings match > * Remove any active filters > * Allow 24 hours for data to fully sync ## Privacy & Data Handling **What Searchable Stores:** * Keyword and page performance metrics * Aggregated statistics only * No personally identifiable information * No individual search query data **Data Retention:** * Pro: 90 days of historical data * Agency: Unlimited retention * Custom: Custom retention windows **Data Deletion:** * Disconnect integration to stop new data sync * Historical data deleted after 30 days * Immediate deletion available on request ## Advanced Features ### Custom Dimensions (Custom) Create custom segments: * By branded vs. non-branded keywords * By commercial intent * By content type * By funnel stage ### Automated Alerts Get notified when: * Ranking drops significantly * New high-volume keywords discovered * CTR falls below threshold * Impressions surge or drop ### API Access Query GSC data via Searchable API: ```javascript Example: Fetch GSC Keywords theme={null} const response = await fetch( 'https://api.searchable.com/v1/gsc/keywords', { headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ projectId: 'your-project-id', dateRange: 'last28days', limit: 100 }) } ); const data = await response.json(); ``` ## Best Practices > [!TIP] > > Sync at least 28 days of data for reliable trends > [!TIP] > > Review GSC insights weekly alongside AI visibility data > [!TIP] > > Use keyword gaps to inform content strategy > [!TIP] > > Monitor both SEO and AEO for complete picture > [!TIP] > > Set up alerts for significant ranking changes > [!TIP] > > Export data monthly for stakeholder reports ## GSC + Searchable Use Cases ### Content Gap Analysis 1. Identify keywords with high impressions, low clicks 2. Check if AI models cite you for those keywords 3. If yes: Optimize for Google (improve title, meta, CTR) 4. If no: Create better content that answers the query ### Competitive Research 1. Find keywords where you rank on page 2-3 2. Check if competitors get AI citations 3. Create superior content optimized for both 4. Track progress in unified dashboard ### ROI Measurement 1. Track organic traffic from GSC 2. Measure AI-referred traffic 3. Calculate conversion rates for each 4. Determine which channel drives more value ## Disconnecting GSC To disconnect Google Search Console: 1. Go to Settings → Integrations 2. Find Google Search Console 3. Click **"Disconnect"** 4. Confirm disconnect **What Happens:** * No new data is synced * Historical data preserved for 30 days * Can reconnect anytime * No impact on your actual GSC account ## Next Steps > **[Google Analytics 4](/integrations/google-analytics-4)** > > Add GA4 for complete traffic insights > **[Keyword Strategy](/using-searchable/working-with-prompts)** > > Optimize your keyword approach > **[Score Benchmarks](/using-searchable/understanding-scores)** > > Master the analytics dashboard > **[API Documentation](/advanced/api-usage)** > > Access data programmatically # MCP Integration Source: https://docs.searchable.com/integrations/mcp Connect Searchable to Claude, ChatGPT, Grok, Gemini, Microsoft Copilot agents, and other MCP-compatible assistants. ## What is MCP? The **Model Context Protocol (MCP)** is an open standard that lets AI assistants connect to external tools and data sources. Searchable's MCP server lets you ask AI assistants questions about your AI visibility, site audits, and content — without leaving the assistant. Compatible clients include: * **Claude** (Claude.ai, Claude Desktop and mobile) and **Claude Code** — add as a connector * **ChatGPT**, **Codex** and **dots** — via ChatGPT's plugin directory; Codex also from its CLI * **Gemini** — via custom Connected Apps; **Gemini CLI** — via the Searchable extension * **Grok** — via custom MCP connectors * **Cursor**, **VS Code**, **Windsurf / Devin** and **Muse Code** * **Microsoft 365 Copilot agents** and **Microsoft Copilot Studio** * **Slack** and **Microsoft Teams** agents — through Claude Tag, Slackbot, Copilot Studio and Microsoft 365 Copilot; see [Use Searchable in Slack and Microsoft Teams](/integrations/slack-and-teams) * **Perplexity Enterprise**, **Mistral**, and other MCP-compatible clients The assistant controls whether custom MCP servers are available for your account, plan, and region. Gemini custom apps currently require a personal Google Account in the US, age 18 or over, English, and Keep Activity turned on. For Microsoft, this compatibility applies to Microsoft 365 Copilot agents and Copilot Studio — not the consumer Microsoft Copilot chat product, which does not currently document a bring-your-own MCP server flow. > [!NOTE] > > Ask questions like *"What's my brand's visibility score on ChatGPT?"* or *"What critical AEO > issues does my site have?"* and the assistant will query Searchable for live data. > [!NOTE] > > **Plan requirement:** MCP integration requires a paid Searchable plan. Both connection methods — > signing in with OAuth and API keys — are unavailable on the Free plan. ## How authentication works The Searchable MCP server uses **OAuth 2.1 with PKCE**. It supports the current MCP client-metadata discovery flow, plus Dynamic Client Registration for clients that still require it. Public clients use PKCE; compatible enterprise clients can use a dynamically issued client secret. This means a new standards-compatible assistant can connect without Searchable adding a provider-specific OAuth integration first. You do **not** paste an API key into your MCP client's config — you sign in and approve access, just like connecting any other app: 1. You add the server URL to your client (`https://app.searchable.com/api/mcp-server/mcp`). 2. On first use, the client opens Searchable in your browser and asks you to **log in** (or reuses your existing session). 3. A **consent screen** shows which application is connecting and lets you choose: * **Which projects** it can access — only the projects you can open in Searchable are listed; pick specific ones, or leave all selected (all also covers projects you create later). * Whether to turn on **Allow write access**. It is off by default, so the connection stays read-only until you switch it on. Projects you can only view are marked **View only**, and when every selected project is view-only the switch is not offered at all: the connection is read-only. 4. You click **Authorize**. Your client receives an OAuth access token scoped to exactly what you approved and uses it for subsequent requests. The token can never exceed what you granted on the consent screen — a read-only, single-project grant stays read-only and single-project. > [!WARNING] > > Only connect the Searchable MCP from trusted clients. Grant the narrowest access you need — > read-only, and only the projects the assistant should see. ## Setup > [!NOTE] > > **Server URL:** `https://app.searchable.com/api/mcp-server/mcp` — the same address for every > client below. ### Step 1: Add the server to your client #### Chat assistants **Claude** Works in Claude.ai and the Claude Desktop app: 1. Open **Customize → Connectors**. 2. Click **+ Add**, then **Add custom connector**. 3. Name it `Searchable` and enter the URL: `https://app.searchable.com/api/mcp-server/mcp` 4. Click **Continue**, keep the recommended sign-in options, and click **Add**. Claude opens the Searchable sign-in and consent page — continue with Step 2. On a Team or Enterprise plan, an Owner first adds the connector under **Organization settings → Connectors**; each member then connects it under **Customize → Connectors**. **ChatGPT** Searchable is listed in ChatGPT's plugin directory: 1. In ChatGPT, open **Plugins** and search for **Searchable**. 2. Open it and connect. ChatGPT opens the Searchable sign-in and consent page — continue with Step 2. Codex and dots use the same plugin connection on your ChatGPT account. **Gemini** Custom apps in the Gemini app are available to personal Google accounts in the US, for users 18 or over, in English, with **Keep Activity** turned on: 1. On [gemini.google.com](https://gemini.google.com), open **Settings → Connected Apps**. 2. Under **Custom apps**, click **Add a custom app** and enter `https://app.searchable.com/api/mcp-server/mcp`. 3. Click **Next** and follow the prompts. Gemini opens the Searchable sign-in and consent page — continue with Step 2. Once connected, Searchable also works in the Gemini mobile app. In a chat, type `@` and pick Searchable. **Grok** 1. Open [grok.com/connectors](https://grok.com/connectors) and select **New Connector → Custom**. 2. Enter `https://app.searchable.com/api/mcp-server/mcp` and complete the sign-in — continue with Step 2. On Grok Business and Enterprise, a team admin first adds the connector in the xAI console before members can use it. #### Coding tools **Claude Code** Add the server from your terminal: ```bash theme={null} claude mcp add searchable --transport http https://app.searchable.com/api/mcp-server/mcp ``` Then inside a session run `/mcp`, pick **searchable → Authenticate** — your browser opens the Searchable sign-in and consent page. Continue with Step 2. Or install the Searchable plugin, which adds the same server: ```bash theme={null} claude plugin marketplace add Searchable-Inc/searchable-plugin claude plugin install searchable@searchable ``` A Searchable connector you added in Claude.ai also appears in Claude Code when you sign in to Claude Code with your Claude account. **Codex** Add the server from your terminal: ```bash theme={null} codex mcp add searchable --url https://app.searchable.com/api/mcp-server/mcp ``` Codex opens the Searchable sign-in and consent page in your browser — continue with Step 2. To sign in again later, run `codex mcp login searchable`. `--url` needs a recent Codex CLI; on an older version, update Codex first. **Cursor** Click to install with one step — Cursor opens, shows the server, and you confirm: [![Add searchable MCP server to Cursor](https://cursor.com/deeplink/mcp-install-dark.png)](cursor://anysphere.cursor-deeplink/mcp/install?name=searchable\&config=eyJ1cmwiOiJodHRwczovL2FwcC5zZWFyY2hhYmxlLmNvbS9hcGkvbWNwLXNlcnZlci9tY3AifQ==) Or add manually to `.cursor/mcp.json`: ```json theme={null} { "mcpServers": { "searchable": { "url": "https://app.searchable.com/api/mcp-server/mcp" } } } ``` Either way, Cursor discovers OAuth automatically — no manual `auth` block needed. On first use it opens the Searchable sign-in and consent page in your browser — continue with Step 2. **VS Code** [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=searchable\&config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fapp.searchable.com%2Fapi%2Fmcp-server%2Fmcp%22%7D) — VS Code opens and asks you to confirm. Or run: ```bash theme={null} code --add-mcp '{"name":"searchable","type":"http","url":"https://app.searchable.com/api/mcp-server/mcp"}' ``` The first time GitHub Copilot uses Searchable, VS Code asks you to sign in — continue with Step 2. **Gemini CLI** Install the Searchable extension: ```bash theme={null} gemini extensions install https://github.com/Searchable-Inc/searchable-plugin ``` Gemini CLI asks you to sign in the first time it uses Searchable (or run `/mcp auth searchable`) — continue with Step 2. **Muse Code** Add Searchable to Muse Code's `~/.config/muse/settings.json` (merge it into the file if you already have one): ```json theme={null} { "schema_version": 1, "mcp_servers": { "searchable": { "transport": "streamable_http", "url": "https://app.searchable.com/api/mcp-server/mcp" } } } ``` Then run `muse mcp login searchable`, which opens the Searchable sign-in and consent page in your browser — continue with Step 2. **Windsurf / Devin** Windsurf is now Devin Desktop. Add the server with the Devin CLI: ```bash theme={null} devin mcp add -s user searchable https://app.searchable.com/api/mcp-server/mcp devin mcp login searchable ``` `devin mcp login` opens the Searchable sign-in and consent page — continue with Step 2. For the Cascade agent, add the server to `~/.config/devin/mcp_config.json` (in the Cascade panel, open **…** → **Open MCP config file**): ```json theme={null} { "mcpServers": { "searchable": { "serverUrl": "https://app.searchable.com/api/mcp-server/mcp" } } } ``` Slack and Teams agents (Claude Tag, Slackbot, Copilot Studio and Microsoft 365 Copilot) have their own steps in [Use Searchable in Slack and Microsoft Teams](/integrations/slack-and-teams). For Perplexity, Mistral, or another hosted assistant, or if a client's menus no longer match the steps above, create a custom MCP server or connector in that product and use `https://app.searchable.com/api/mcp-server/mcp`. Searchable publishes OAuth discovery metadata, so choose OAuth or automatic authentication when the host asks. The exact menu name and availability are controlled by the host product. ### Step 2: Log in and authorize The first time your client connects, Searchable opens in your browser: 1. **Log in** to Searchable (or continue with your existing session). 2. On the **consent screen**, choose which projects the client may access. Leave **Allow write access** off (the default) for read-only access, or switch it on to allow write tools. 3. Click **Authorize**. Your browser returns to the client, now connected — no API key to copy or paste. > [!NOTE] > > Your login is the identity for this flow. To connect a script or a client that only accepts a > static bearer token, use the legacy API-key path below instead. ### Step 3: Verify the connection Once connected, ask: ``` List my Searchable projects ``` You should see a list of your projects with their names and domains. ### Connect with an API key (legacy) The OAuth login flow above is the recommended path for interactive MCP clients. For scripts, headless clients, or any client that only accepts a static bearer token, a Searchable **API key** works as a bearer token directly — the same `sea_` key the [REST API](/advanced/api-usage) accepts. **1. Create an API key** In **Manage Account → Integrations → MCP → Access tokens**, click **Generate access token**, name it, and copy the token (starts with `sea_`). Store it securely — you won't see it again. A token can cover all your projects or be limited to a single project. It's read-only unless you turn on **Allow write access**, which lets it use every write tool, including the ones that need a capability. A token's access can't be changed later, so create a new one to add write access. **2. Send it as a bearer token** Configure your client to send `Authorization: Bearer sea_...` on every request to `https://app.searchable.com/api/mcp-server/mcp`. No login or consent screen is involved on this path. ## Available tools The Searchable MCP exposes **49 primary tools** — 30 read-only and 19 write (51 with the Associated Sources feature enabled), plus 4 deprecated names kept callable through their sunset window (see [Deprecated tool names](#deprecated-tool-names)). Every read tool is annotated `readOnlyHint: true`. Every tool that operates on a project takes a required `projectId` (get IDs from `list_projects`), and most read tools also accept an optional `response_format` — `"concise"` (default, summary + capped rows) or `"detailed"` (fuller rows, same shape). > [!NOTE] > > **Two kinds of write tool.** The original three ([Write actions](#write-actions) below) need at > most the **write** scope from your connection (`generate_report` only **read**). The write tools > under [Project and prompt management](#project-and-prompt-management), and `import_prompts`, each > need a connection minted with a **specific named capability** instead (e.g. `prompt:draft`, > `knowledge:manage`) — a plain read-and-write connection does not automatically reach any of them. > An API key carries exactly the scopes it was created with: **write** and every capability when > **Allow write access** was on, read-only otherwise. An OAuth connection gets a capability only > when the connecting client asked for that exact capability at authorization **and** you granted it > on the consent screen: **Allow write access** grants every capability the client asked for, and > **Customise permissions** picks them one by one. Calling one without the right capability returns > `missing_scope`, distinct from `forbidden` (the connection has the capability, but your own role > in the workspace doesn't allow the action) and `not_found` (the project isn't visible to this > connection at all, or the feature isn't enabled for it) — the three read differently on purpose, > so you know which of "wrong grant," "wrong role," or "wrong project" to fix. The server also publishes two **resources** — `searchable://glossary` (compact index) and `searchable://glossary/full` (complete definitions) — metric and concept definitions with the tools that read each one, for hosts that attach resources as reference context. `get_visibility`, `get_share_of_voice`, and `get_sentiment` additionally accept `compare: "previous_period" | "previous_year"` — the response gains a `comparison` block with the previous window's metrics and current-minus-previous deltas, so period-over-period questions are one tool call. > [!NOTE] > > **Absolute date ranges.** The core analytics reads accept `from` and `to` (inclusive, UTC) as an > alternative to the relative `days` — `from: "2026-03-01", to: "2026-03-31"` answers "how did March > go" without arithmetic against today. Both bounds are required together, and they are **mutually > exclusive with `days`**: sending both is an `invalid_argument` error rather than a silent > precedence rule. Bare `YYYY-MM-DD` and full ISO timestamps are both accepted (the UTC day is what > counts). An over-long `days` is clamped to the tool's maximum, but an over-long explicit range is > **rejected** — narrowing a range you spelled out would answer a different question than the one > you asked. Supported on `get_visibility` (every `group_by`), `get_share_of_voice`, > `get_sentiment`, `get_topic_analysis`, `get_ai_traffic` (every `report`), and `investigate`, plus > the matching REST endpoints. Tools not in that list — `get_competitors`, > `get_shopping_visibility`, `get_ads`, `get_page_metrics`, `get_query_fanout`, > `get_prompt_answers`, and the source reads — still take `days` only. (`get_gsc_performance` has > always had its own `startDate` / `endDate`.) > [!NOTE] > > **Filter echo.** Every filterable read echoes an `appliedFilters` object: the platform / brand / > topic / location filters that call actually narrowed by, or `{}` when it was unfiltered. That > covers `get_visibility`, `get_share_of_voice`, `get_competitors`, `get_topic_analysis`, > `list_prompts`, `get_sentiment` (every view), `get_source_trends`, and `get_shopping_visibility` > (`summary` view). All of them except `get_topic_analysis` also return `availableFilters` (the > project's topics and countries) so a client can offer the next filter without a second round-trip > — on `get_visibility` that rides along on the `summary` and `platform` views, on > `get_share_of_voice` on the `summary` view, and on `get_competitors` on the list view. Use > `appliedFilters` to state what a number covers rather than presenting a filtered figure as the > whole picture. > [!NOTE] > > Write actions **are** MCP tools too: `trigger_audit` and `refresh_sitemap` are available to > keys/grants with the **write** scope, and `generate_report` to any connection, read-only included, > as sharing a report is in the app. `generate_report` and `refresh_sitemap` require an explicit > `confirm: true` argument — without it they return a dry-run preview and change nothing. > `trigger_audit` starts straight away, as the app's button does; pass `dryRun: true` to preview it > first. The REST equivalents (e.g. `POST /api/mcp/projects/{projectId}/reports`) remain available; > see [Advanced API Usage](/advanced/api-usage). ### Orientation Four tools exist purely to make assistants reliable, and are worth calling early in a session. None of them takes a `projectId`. > **get\_current\_date** > > The server's current UTC date and time (`utcDate`, `utcDateTime`, `dayOfWeek`, `isoWeek`) plus pre-computed window starts — `yesterday`, `last7Days`, `last30Days`, `last90Days`, `last365Days`. Call it before interpreting relative time like "last month" so date math is never guessed. Needs no scope. > > **Parameters:** none > **whoami** > > Who this connection is authenticated as, its scopes, whether it is limited to specific projects, the workspaces it can reach, and the project roster it can see. Each workspace lists your role, whether you can create projects there (`canCreateProjects`), and the ids of its projects. Call it when unsure what the connection can access, and before `create_project`, which needs one of those workspace ids. Over REST, `GET /api/mcp/workspaces` returns the same list. > > **Parameters:** none > **search\_docs** > > Keyword search over this documentation, so questions like "how is the visibility score calculated?" are answered in-chat, from the source. Returns each hit's `slug`, title, description and a snippet — follow up with `read_doc`. Needs no scope. > > **Parameters:** `query` (required), `limit` (default 8, max 25) > **read\_doc** > > One documentation page by its slug (from `search_docs`, e.g. `recipes/visibility-drop`), optionally narrowed to one heading with `section`. A `section` that doesn't match returns the whole page with `sectionNotFound` and the page's `availableSections`; an unknown slug returns `not_found`. Needs no scope. > > **Parameters:** `slug` (required), `section` (optional) ### Diagnostics > **investigate** > > Answers a whole diagnostic **question** in one call, by running its multi-read chain server-side > and in parallel. Prefer it over chaining the granular tools yourself for "why did X change" and > "where do we stand" questions. > > `question="visibility_drop"` (default) returns the score trend with its largest report-to-report > fall (`spanDays` says how many days that covers when reports aren't daily), per-platform movement > against the previous window, the share-of-voice shift with which competitors gained, and the five > weakest of the ten topics you're mentioned in most. `"competitive_position"` returns your standing > with ranks and deltas, your score trend, and up to five topics — of the 15 you're mentioned in > most — where a rival currently leads. `"citation_gap"` returns your citation trend, the domains AI > answers actually cite in your space, and per-engine citation share. > > Takes the usual narrowing arguments — `days` or `from`/`to`, `platform`, `topicId`, `unbranded` / > `branded`, `country` / `locationId` — but not every part of the answer honors all of them: the > score trend takes only `unbranded` / `branded`, and the topic figures ignore `topicId` and > location. > > **It reports measurements, never a cause.** It will tell you ChatGPT fell 38 points while Gemini > held; it will not tell you why, because the reason is usually outside the data — a rebrand, a > campaign ending, a competitor launch. Every response carries an `interpretation` list naming what > the numbers cannot settle. A signal whose upstream failed appears in `unavailable` with a reason > rather than as a zero, so missing data is never mistaken for a finding. > > Each question has a companion recipe explaining how to read the result and when it misleads: > [visibility drop](/recipes/visibility-drop), [competitive position](/recipes/competitive-position), > [citation gap](/recipes/citation-gap). Load one in-chat with > `read_doc("recipes/visibility-drop")`. > > **Parameters:** `projectId` (required), `question` (`"visibility_drop"` by default, > `"competitive_position"`, or `"citation_gap"`), `days` (default 30, max 365), `from`/`to`, > `platform`, `topicId`, `unbranded`, `branded`, `country`, `locationId` (all optional except > `projectId`) ### Projects > **list\_projects** > > List all projects this connection can access — names, domains, IDs, and `dataAvailableFrom` (the earliest date with visibility data, `null` while a project has none yet). Archived projects are listed too, marked "(archived)". Call this FIRST: every project-scoped tool needs a `projectId`, and `dataAvailableFrom` tells you how wide a window is worth requesting. > > **Parameters:** `response_format` (optional) ### Visibility & share of voice > **get\_visibility** > > AI visibility for a project. `group_by` selects the view: `summary` (overall visibility score), `platform` (per ChatGPT/Claude/Gemini/Perplexity breakdown), `prompt` (per-prompt breakdown, paginated), `location` (per country/city), or `date` (the per-report time series — trend, uplift, before/after analysis; default 90-day window). The `unbranded`/`branded`, `topicId`, `country` and `locationId` filters work on `summary`, `platform`, `prompt` and `location`, and `date` accepts only `unbranded`/`branded` (anything else is an `invalid_argument`). The `platform` filter narrows only `group_by=prompt|location` — on `summary`/`platform` the figures stay all-platform. An interactive Visibility Snapshot card also renders automatically beside the answer on Apps-capable clients — see below. > > > [!WARNING] > > > > `group_by="topic"` was **removed**. Per-topic visibility now lives on its own tool, `get_topic_analysis` (next card), which returns the competitive rank, strengths, and gaps the old view never had. Passing `group_by="topic"` is now a validation error — switch the call to `get_topic_analysis`. > > **`include`** attaches optional extra blocks (comma-separated): `industry_ranking` adds the competitive leaderboard — the **top 25** entities by visibility, with your own rank, share of voice, and change — to `group_by=summary|platform` (`hasMore` flags a longer field; `brandRankStatus` distinguishes ranking below the cutoff from being unranked, and `totalEntities` is null when truncated); `annotations` adds the dated markers (campaign launches, migrations) overlapping the window to `group_by=date`, so a spike comes back with its explanation attached. An `include` a view cannot attach is an `invalid_argument` error on **every** view — including `group_by=prompt` and `group_by=location`, which attach no blocks at all — never a response that quietly lacks the block. > > **Parameters:** `projectId` (required), `group_by` (`"summary"` | `"platform"` | `"prompt"` | `"location"` | `"date"`, default summary), `days` (default 30, or 90 for `group_by=date`; max 365, or 180 for `group_by=prompt`), `from`/`to` (same maximums), `include` (`industry_ranking` on summary/platform, `annotations` on date), `compare` (summary/platform only), `unbranded`, `branded`, `platform` (`group_by=prompt|location`), `topicId`, `limit` (`group_by=prompt`, default 100, max 500), `offset` (`group_by=prompt`), `country`, `locationId`, `response_format`, `render` (all optional except `projectId`) > **get\_topic\_analysis** > > Per-topic AI visibility — the brand's mention rate in each topic, its competitive rank within that topic, how many of its tracked prompts were answered in the window, and the AI-identified attribute strengths and gaps behind the number. This is the **only** per-topic view: use it for any question about topics, subject areas, or categories, including where a brand is winning or losing and what to fix. Replaces the removed `get_visibility (group_by:"topic")`. An interactive Topic Analysis card also renders automatically beside the default `view=topics` answer on Apps-capable clients — see below. > > **`view="prompts"`** drills into ONE topic (`topicId` required): every prompt assigned to it with your average rank, its visibility, and which competitors outrank you — the "which specific questions are we losing" view behind a weak topic. A prompt you never appeared for reports `rank: null`, never `0`. > > **`view="heatmap"`** returns average brand position per topic x AI platform — where you're strong on one engine and absent on another. There is deliberately no platform filter on this view: the platform axis is the answer. > > **Parameters:** `projectId` (required), `view` (`topics` default, `prompts`, `heatmap` — `platform` is rejected on `heatmap`, whose axis IS platform), `topicId` (required for `view=prompts`, optional on `heatmap`; rejected on the default view, which lists every topic), `days` (default 30, max 365), `from`/`to`, `limit` (`view=topics`, default 15, max 50 — keeps the topics with the most brand mentions), `platform`, `unbranded`, `branded`, `response_format`, `render` (all optional except `projectId`) > **get\_visibility\_history** > > **Deprecated** — call `get_visibility` with `group_by: "date"` instead (same data, same parameters). This name keeps answering with an identical payload until its removal date; see [Deprecated tool names](#deprecated-tool-names). > > **Parameters:** `projectId` (required), `days` (default 90, max 365), `unbranded`, `branded`, `response_format` (all optional except `projectId`) > **get\_share\_of\_voice** > > Brand vs competitor share of voice — mention-share percentage and rank among all tracked entities (the brand plus the top 50 competitors; `totalCompetitors` gives the full count), plus each entity's day-over-day (today vs yesterday) point change. `mentions`/`citations` aren't part of this view (mirrors the in-app Share of Voice sheet) — use `get_competitors` for those. An interactive Share of Voice card also renders automatically beside the answer on Apps-capable clients — see below. > > **`group_by="date"`** returns the daily share series instead — your share and your top 10 competitors', one point per day. That is the "is our share growing?" view; `include="annotations"` adds the dated markers explaining any jump. `compare` is not supported there (the series already shows the movement), and the interactive card hides itself on it. > > **Parameters:** `projectId` (required), `group_by` (`summary` default, or `date`), `days` (default 30, max 365), `from`/`to`, `include` (`"annotations"`, `group_by=date` only — any `include` on the summary is an `invalid_argument`), `platform`, `topicId`, `unbranded`, `branded`, `country`, `locationId`, `compare` (summary only), `response_format`, `render` (all optional except `projectId`) ### Competitors > **get\_competitors** > > The project's real/tracked competitors, **ordered by rank (highest visibility first)** — visibility, share of voice, sentiment score, average mention position, all-time mention count (`mentions`) and in-window mention count (`mentionsInWindow`), plus a `brand` block carrying the same metrics for the project itself so the two sit on one ruler. Each row's `id` is what `update_competitor` takes. Pass `competitorId` for a **head-to-head** against one competitor instead: `headToHead.brand` vs `headToHead.competitor`, each with visibility %, share of voice %, sentiment (0–100) and average position (a rank — **lower is better**), plus the phrases each is known for; for the competitor it also returns its rank, per-day history, and up to 10 recent prompts it was mentioned in. `competitorId` accepts either the competitor's ID or its **name exactly as the list shows it** (`competitorId: "Nike"`), so you can drill in straight from the list. A `null` metric means unmeasured in that window, not zero. `visibility`/`sov`/`sentimentScore`/`avgPosition`/`mentionsInWindow` honor the filters; the list view's `mentions` (the `brand` block's included) is all-time and ignores them. On the head-to-head, `citations` (inline citations on responses mentioning the competitor) and `history[].domainSources` (source URLs — pages used for generation, not inline citations — on the competitor's own domain) are different metrics; `history[]` honors `days` only, and the competitor's mention and citation totals cover at most its 500 most recent in-window mentions. An interactive Competitor Ranking card also renders automatically beside the answer on Apps-capable clients, in whichever shape matches the call — see below. > > **Parameters:** `projectId` (required), `competitorId` (optional — ID or name; switches to the head-to-head view), `days` (default 30, max 365), `platform`, `topicId`, `unbranded`, `branded`, `country`, `locationId`, `limit` (list view, default 20, max 100), `offset` (list view), `response_format`, `render` (all optional except `projectId`) ### Sources & citations > **search\_sources** > > Top cited source domains from AI answers — rank, citation counts, usage %, models, and top content types. Paginated and sortable. Use to find which external sites AI models cite most; drill in with `get_source_detail`. > > **Parameters:** `projectId` (required), `sort` (`citations` | `usage` | `avgCitations`, with a `-` prefix for descending; default `"-citations"`), `limit` (default 20, max 1000), `offset`, `days` (default 30; rounded up to 7, 30, 90 or 365), `platform`, `topicId`, `sourceType` (comma-separated: `brand`, `competitor`, `editorial`, `social`, `forum`, `review`, `institutional`, `third_party`, `other`), `unbranded`, `branded`, `country`, `locationId`, `response_format` (all optional except `projectId`) > **get\_source\_detail** > > Drill into cited sources. `level="domain"` (+ `domain`): domain summary, or `include="urls"` (cited URLs on it) / `include="responses"` (AI responses citing it). `level="url"`: with `url` → single-page citation analytics, or `include="content"` → cached page markdown + entity mentions (never triggers a live scrape); without `url` → all cited URLs across the project (optionally filtered by `contentType`). A missing `domain` (or a missing `url` with `include="content"`) returns `not_found`. > > **Parameters:** `projectId` (required), `level` (`"domain"` | `"url"`, default domain), `include` (`"urls"` | `"responses"` on `level=domain`; `"content"` on `level=url`), `domain` (required for `level=domain`), `url` (`level=url`; with `include="responses"` it narrows to responses citing that one page), `contentType`, `limit` (default 20, max 1000), `offset`, `cursor`, `days` (default 30; rounded up to 7, 30, 90 or 365), `platform`, `topicId`, `sourceType`, `unbranded`, `branded`, `country`, `locationId`, `response_format` (all optional except `projectId`) > **get\_source\_trends** > > How the project's top source **domains** trend over time — a per-domain time-series of the external sites most used as sources in AI answers, over the window (default 30 days). An interactive Sources card renders beside the answer on Apps-capable clients: the trend line inline, expanding to the top source URLs. > > **Parameters:** `projectId` (required), `days` (window; default 30 — pass `7`/`90`/`365` for a different one), `platform`, `topicId`, `sourceType`, `unbranded`, `branded`, `country`, `locationId`, `response_format`, `render` (all optional except `projectId`) ### Sentiment > **get\_sentiment** > > Brand sentiment across AI platforms. `view` selects: `summary` (default) — overall score + positive/neutral/negative split + per-platform breakdown, plus `analysis` (the weekly brand-perception write-up for the platform that mentions you most, or the one you filter to with `platform`) and `analyses` (every platform's write-up, or only the filtered platform's when `platform` is set); `history` — the daily sentiment trend (default 90-day window); `competitors` — head-to-head vs competitors, the brand flagged by `isBrand`. `topicId` is not accepted on `competitors`, `compare` only on `summary`, and `include` (`annotations`) only on `history`; each is an `invalid_argument` elsewhere. The interactive Sentiment Profile card renders on `summary` answers only. > > **Parameters:** `projectId` (required), `view` (`"summary"` | `"history"` | `"competitors"`, default summary), `days` (default 30, or 90 for `view=history`; max 365), `from`/`to`, `platform`, `topicId`, `limit` (`view=competitors`, default 10, max 50), `include` (`"annotations"`, `view=history` only), `compare` (`"previous_period"` | `"previous_year"`, `view=summary` only), `unbranded`, `branded`, `country`, `locationId`, `response_format`, `render` (all optional except `projectId`) > **get\_sentiment\_history** > > **Deprecated** — call `get_sentiment` with `view: "history"` instead (same data, same parameters). Keeps answering identically until its removal date; see [Deprecated tool names](#deprecated-tool-names). > > **Parameters:** `projectId` (required), `days` (default 90, max 365), `platform`, `topicId`, `unbranded`, `branded`, `country`, `locationId`, `response_format` (all optional except `projectId`) > **get\_sentiment\_competitors** > > **Deprecated** — call `get_sentiment` with `view: "competitors"` instead (same data, same parameters). Keeps answering identically until its removal date; see [Deprecated tool names](#deprecated-tool-names). > > **Parameters:** `projectId` (required), `days` (default 30, max 365), `platform`, `limit` (default 10, max 50), `unbranded`, `branded`, `country`, `locationId`, `response_format` (all optional except `projectId`) ### Query fanout > **get\_query\_fanout** > > The sub-queries AI models generate when answering tracked prompts. Without `prompt_id`: the project aggregate (most frequent fanout queries, per-prompt counts, per-model stats; paginated). With `prompt_id`: every deduped sub-query for that one prompt, with per-query frequency and query type. Paid feature — returns `plan_upgrade_required` if not entitled. > > **Parameters:** `projectId` (required), `prompt_id` (optional — note the snake\_case; the prompt's id from `list_prompts`, a unique id prefix, or its text; omit for the project aggregate), `days` (default 30, max 180), `platform`, `topicId`, `unbranded`, `branded`, `limit` (aggregate, default 50, max 50), `offset` (aggregate; rounded down to a multiple of `limit`), `topQueriesLimit` (aggregate, default 50, max 200), `response_format` (all optional except `projectId`) ### Brand profile & authority > **get\_brand\_profile** > > The project's knowledge-base brand profile (name, website, headline, category, positioning, description, topics, business type, reach, language, exact match, disambiguation note, connected entities, competitor geography/context) plus the latest batch of **AI-observed brand facts** — statements AI platforms actually make about the brand, each with its platform and verification status. Pass `include: "domain_authority"` to also get the Moz domain-authority block (latest DA/PA/spam/linking-domains snapshot + roughly-monthly 365-day history) in the same call. Call this first for brand context in any analysis. `profile` is `null` until onboarding creates one; facts come from the weekly sentiment job. > > **Parameters:** `projectId` (required), `platform`, `verificationStatus` (`"unverified"` | `"pass"` | `"fail"`), `include` (`"domain_authority"`), `response_format` (all optional except `projectId`) > **get\_domain\_authority** > > **Deprecated** — call `get_brand_profile` with `include: "domain_authority"` instead (same data; the REST endpoint keeps custom `days` windows). Keeps answering identically until its removal date; see [Deprecated tool names](#deprecated-tool-names). > > **Parameters:** `projectId` (required), `days` (default 365, max 1095) (all optional except `projectId`) ### Prompts > **list\_prompts** > > The project's prompt catalog as configured — tracked prompts (type, intent, branded flag, topics, tracked locations, keyword volume/difficulty), paused (untracked) prompts, or the AI-suggested review queue (`status="suggested"` — proposals from research runs and the agent that nobody has accepted or rejected yet, each with the reason it was proposed). Configuration data, not performance — use `get_visibility group_by=prompt` for per-prompt visibility scores. The summary always reports all three counts, so pending suggestions surface from any call. Tracked and paused rows carry each prompt's full `id`, which the prompt-scoped tools accept. An interactive Prompts card also renders automatically beside the answer on Apps-capable clients. > > **Parameters:** `projectId` (required), `status` (`"tracked"` | `"untracked"` | `"suggested"` | `"all"` — tracked + untracked, never suggested; default tracked), `topicId`, `search` (case-insensitive substring of the prompt text), `limit` (default 50, max 1000), `offset`, `days` (metrics window, default 30, max 365), `sortBy` (`prompt` | `volume` | `difficulty` | `branded` | `intent` | `recency`, default recency — performance columns like visibility/position/sentiment are computed per page and are **not** server-sortable), `sortDir` (`"asc"` | `"desc"`, default desc; the default `recency` sort is always newest first), `unbranded`, `branded`, `response_format`, `render` (all optional except `projectId`; `status="suggested"` honors only `topicId`, `search`, `limit` and `offset`, newest first) > **get\_prompt\_answers** > > Raw, per-response AI-answer data for one prompt (Profound-parity) — platform, response date, response text, whether the brand was mentioned, competitors mentioned, and the source URLs the engine consulted (`sources[]` — pages used for generation, not inline citations), one entry per AI response. Each answer also shows the exact wording it was asked (`asked_as`, location included), which can differ from the prompt's current wording after an edit; an older answer whose wording was not recorded is marked when the prompt has been reworded since. Not an aggregate — use `get_visibility` or `get_share_of_voice` for scores/rankings. The default concise table shows a 120-character snippet per answer; `response_format: "detailed"` prints every answer in full (inline base64 images removed, 50,000-character safety cap). `promptId` takes the prompt's id from `list_prompts`, a unique id prefix, or the prompt's text (matched ignoring case, spacing and trailing punctuation, and also when cut short with a trailing `…` the way `list_prompts` and the other tables print it); text matching several prompts returns `invalid_argument` listing their ids, and one matching none returns `not_found`. > > **Parameters:** `projectId` (required), `promptId` (required — note the camelCase, unlike `get_query_fanout`'s `prompt_id`), `days` (default 30, max 365), `platform`, `limit` (default 10, max 50), `offset` (pass the previous call's `nextOffset` to page), `response_format` (all optional except `projectId` and `promptId`) ### Site health & audits > **get\_site\_health** > > Technical + AEO site health. `view="pages"` (default) = the monitored-page inventory, **one server-paginated page at a time** (audited pages first) — every page (audited or not) with its latest technical/AEO scores, open-issue count, and last-audited date, plus `pagination.total`; page with `limit`/`offset`, filter server-side with `search` (URL/slug/title), reorder with `sortBy`, and omit the header aggregates with `includeSummary=false`; `view="audits"` = per-page technical/AEO scores for audited pages + averages; `view="issues"` = issues grouped by severity (filter `status`); `view="issue_details"` = open issues grouped by type with fix guidance (filter `issueType`; covers the first 1,000 open issues). An interactive Site Audit card renders beside the answer on Apps-capable clients: `pages`/`audits` show the inventory with a **search box** + **Prev/Next** paging (both server-side), a per-page **Audit** button (calls `trigger_audit` for that page), a per-page **Issues** button (that page's open and resolved issues), and a **Sync website** button (calls `refresh_sitemap`); `issues`/`issue_details` show the findings ranked by severity, each expanding to its description and affected pages (plus fix guidance on `issue_details`). > > **Parameters:** `projectId` (required), `view` (`"pages"` | `"audits"` | `"issues"` | `"issue_details"`, default pages), `status` (`view=issues`: `"open"` | `"resolved"` | `"all"`, default open), `pageId` (`view=issues` / `issue_details`: scope to one monitored page — the card's **Issues** button), `issueType` (`view=issue_details`: `"technical"` | `"content"`), `includeIssues` (`view=audits`, boolean), `search` (`view=pages`: server-side filter by URL / slug / title), `sortBy` (`view=pages`: `"audited"` | `"technical"` | `"aeo"` | `"issues"`, default audited-first), `includeSummary` (`view=pages`, boolean, default true), `limit` (`view=pages` default 12, max 100; `view=issues` default 1000, max 5000), `offset` (`view=pages` / `view=issues`), `response_format`, `render` (all optional except `projectId`) ### Content & articles > **list\_articles** > > List articles / content pieces for a project (merges content-v2 + legacy), most recently updated first. Shows title, status, type, word count, and source, plus total and per-status counts. Not paginated: one call returns at most 100 rows. > > **Parameters:** `projectId` (required), `status` (`"draft"` | `"published"` | `"editing"` | `"all"`, default all — `all` excludes archived pieces), `limit` (default 50, max 100), `response_format` (all optional except `projectId`) > **get\_article** > > Full article content — HTML/markdown body, SEO metadata (meta title and description, OG image, schema markup), FAQs, internal links, and all metadata. Legacy articles also carry an outline, TL;DR, summary, alt text, and external links; content-v2 pieces return those empty. > > **Parameters:** `articleId` (required — the `id` from `list_articles`) ### Opportunities > **get\_opportunities** > > Actionable opportunities Searchable surfaced for the project — prioritized fix / write / audit / setup suggestions derived from visibility, mentions, sentiment, sources, traffic, prompts, prompt research, site health, articles, and platform setup. Returned in the product's priority order (status, then impact, then newest). Defaults to active opportunities; blocked for active pitch projects. Each row carries its `id`, which `update_opportunity_status` takes. > > **Parameters:** `projectId` (required), `status` (`"active"` | `"completed"` | `"dismissed"` | `"auto_resolved"`, default active only), `includeResolved` (boolean — with no `status`, returns every status), `source` (`"visibility"`, `"mentions"`, `"sentiment"`, `"sources"`, `"traffic"`, `"prompts"`, `"prompt_research"`, `"site_health"`, `"articles"`, `"platform"`), `impact` (`"critical"` | `"high"` | `"medium"` | `"low"`), `limit` (default 100, max 500), `offset`, `response_format` (all optional except `projectId`) ### GA4 / Traffic > [!NOTE] > > This tool requires a linked GA4 property. When the project has none, the backing endpoint returns a `409` error with `{ code: "ga4_not_connected", message, howToFix }` — the `howToFix` field points at **Settings → Integrations** in Searchable, where you can connect GA4 and then retry. > **get\_ga4\_traffic** > > Google Analytics 4 website traffic. `report="ai_referrals"` (default; per-LLM-host AI Source Visitors from ChatGPT/Copilot/Gemini/Perplexity/DeepSeek and more — use for AI-referral attribution), `"trend"` (daily users/sessions/pageviews), `"top_pages"` (top landing pages by sessions), or `"sources"` (channel-group + source breakdown). > > **Parameters:** `projectId` (required), `report` (`"ai_referrals"` | `"trend"` | `"top_pages"` | `"sources"`, default ai\_referrals), `weeks` (default 12, max 52), `limit` (`top_pages`/`sources`, default 20, max 100), `response_format` (all optional except `projectId`) ### Search Console (GSC) > [!NOTE] > > This tool requires a linked Google Search Console site. When the project has none, the backing endpoint returns a `409` error with `{ code: "gsc_not_connected", message, howToFix }` — the `howToFix` field points at **Settings → Integrations** in Searchable, where you can connect GSC and then retry. > **get\_gsc\_performance** > > Live Google Search Console organic performance. `report="overview"` (default): clicks/impressions/CTR/average position plus a prior-period comparison. `"top_queries"` / `"top_pages"` / `"countries"` / `"devices"`: ranked rows for that dimension. `"trend"`: daily timeseries with window totals. `"opportunities"`: quick-win queries (ranking position 4-15, ≥100 impressions, under 5% CTR), sorted by impressions and drawn from the site's top 250–1,000 queries. `"position_buckets"`: query-count distribution across top 3 / page 1 / page 2 / beyond 20, over the top 1,000 queries. Pass `period` (`"7d"`|`"28d"`|`"30d"`|`"90d"`|`"16m"`, default `"30d"`; an unrecognized value falls back to `"30d"`) or an explicit `startDate`+`endDate` (`YYYY-MM-DD`, both together, start on or before end — otherwise `invalid_argument`). GSC data lags \~3 days: an explicit range is clamped to the last day Google has published and to its 16-month retention, and the response's `window` then carries `clamped`/`requestedTo` (or `truncatedStart`/`requestedFrom`). > > **Parameters:** `projectId` (required), `report` (default overview), `period`, `startDate`, `endDate`, `limit` (`top_queries`/`top_pages`/`countries`/`opportunities` only; defaults 25/25/100/20, max 250/250/250/100), `response_format` (all optional except `projectId`) ### AI Traffic > [!NOTE] > > First-party AI-traffic measurement from the tracker/CDN pipeline — Searchable's flagship differentiator, distinct from GA4 above (which reads Google's own analytics). Six `get_ai_traffic` reports (`overview`, `crawlers`, `referrals`, `top_cited_pages`, `human`, `correlation`) need any connected source — crawler logs, a CDN integration, the Searchable tracker, or a linked GA4 / Search Console / Bing site; `sitemap_coverage`, `attribution`, and `logs` need a crawler-log or CDN source specifically. When the required source is missing, the backing endpoint returns a `409` error with `{ code: "traffic_not_connected", message, howToFix }` — the `howToFix` field points at **AI Traffic → Setup** in Searchable, where you can connect a source and then retry. `get_page_metrics` degrades instead of failing: a block it can't read comes back as `available: false` with a `reason`. > **get\_ai\_traffic** > > First-party AI-crawler and AI-referral traffic. `report="overview"` (default): crawler + AI-referral totals by platform, plus the top crawled pages. `"crawlers"`: per-page AI-crawler activity, capped by `limit` (no deep pagination at the tool layer — use the REST endpoint's `offset` for that). `"referrals"`: daily AI-referral session timeseries by platform. `"top_cited_pages"`: top pages driving AI-referral traffic for one platform — `platform` is **required** for this report. `platform` is validated strictly, and an unrecognized value returns an error rather than a silent empty result: `crawlers` and `referrals` accept all eight platform IDs (`openai`, `anthropic`, `google`, `perplexity`, `microsoft`, `deepseek`, `xai`, `meta`), while `top_cited_pages` accepts only the first five. **`platform` filters only `crawlers`, `referrals`, and `top_cited_pages`** — `overview` always returns every platform, and the five further reports below ignore it too, so filter with `crawlers` or `referrals` instead. Long-tail vendors that can appear in *output* rows (e.g. `amazon`, `you.com`) are deliberately not filterable. > > `overview`, `crawlers`, and `correlation` take `groupBy="platform" | "botCategory" | "botName"`. This extends the same tool rather than adding traffic tools. Omitting it preserves the legacy response; `botCategory` adds `byCategory`, `botName` adds `byBot` (name, ID, vendor, and category), and `platform` adds correlation's per-page `byPlatform` crawl split. Overview and crawlers keep their existing `byPlatform` arrays with every grouping. > > The public bot categories are `ai_training`, `ai_search`, `ai_assistant`, `ai_agent`, and `search_engine`. `ai_assistant` is the distinct user-triggered class for ChatGPT-User, Claude-User, and Perplexity-User. A per-page `ai_training → ai_search → ai_assistant` view is therefore possible, but `ai_search` means retrieval/search crawling — actual answer citation remains a separate visibility metric. > > **Five further reports** cover the rest of the AI Traffic surface: > > * **`sitemap_coverage`** — per bot (named, with vendor and category): how often it fetched your sitemap, how many listed pages it discovered, and the median lag from a page appearing in the sitemap to its first crawl. It answers how *fast and how completely* each crawler is picking your sitemap up; it does not enumerate individual uncrawled URLs. > * **`human`** — real human sessions that arrived FROM an AI assistant, split by platform. The conversion half of the funnel the crawler reports open. **Check `summarySource`:** site-wide totals come from the first-party tracker while the AI figures fall back to GA4 when no tracker is installed — in that split state every `summary` field and `percentOfTotal` are `null` (not `0`), and a failed GA4 read surfaces as `aiReferral.sourceError` rather than as zeroes. > * **`correlation`** — per page, crawls and AI-referred sessions over the **same window**, joined by path. Its optional grouping classifies the crawl side only. This is co-occurrence, not attribution: nothing establishes that a session followed a crawl or came from a named bot. Ranked by sessions, but the ranked population is the upstream union of the top-500 crawled and top-500 human-traffic pages — `pagesConsidered` is a floor, and `populationTruncated` tells you when pages are certainly missing. > * **`attribution`** — traffic by UTM tuple, referrer domain, and bot category. The underlying query stops at 100 rows; `truncated: true` marks `totalRows` as a floor. > * **`logs`** — the raw request feed, filterable by `path`, `statusCode`, `botsOnly`, and `host`, cursor-paginated: pass a page's `nextCursor` back as **both** `cursorTimestamp` and `cursorEventId` (over MCP, sending only one half is ignored and page one comes back again; the REST endpoint rejects it). Row-capped (25 default, 100 max) because it is the one row-level report. **`ip_address` and `user_agent` are never returned** — the IP is personal data with no analytical value once `country` is present, and the user-agent is superseded by the resolved bot identity. > > **`host`** narrows any report to specific hostnames (comma-separated) — useful on multi-domain projects where "blog versus docs" is the question. > > **Parameters:** `projectId` (required), `report` (default `overview`), `days` (default 30, max 365), `from`/`to`, `host`, `platform` (required for `top_cited_pages`; used only by `crawlers` / `referrals` / `top_cited_pages`), `groupBy` (`overview`/`crawlers`/`correlation` only), `limit` (default 20 for `crawlers`, 10 for `top_cited_pages`, 25 for `correlation` / `attribution` / `logs`; max 100), plus `path` / `statusCode` / `botsOnly` / `cursorTimestamp` / `cursorEventId` for `report=logs`, `response_format` (all optional except `projectId`) > **get\_page\_metrics** > > The per-URL rollup: one page's AI citations (`inlineCitations` — answers linking it in their text — and `sourceUses` — answers that consulted it), AI-referral sessions by platform, and AI-crawler visits by bot, as daily buckets in one call. `url` takes a full URL or a bare path such as `/pricing`, resolved against the project's domain. Each block degrades on its own to `available: false` with a `reason` — `traffic_not_connected`, `external_url` (the host isn't one of the project's verified domains), or `citation_lookup_failed` — and a page that was never cited returns zeros, not an error. An empty or invalid URL, or a bare path on a project with no domain, returns `invalid_argument`. > > **Parameters:** `projectId` (required), `url` (required), `days` (default 90, max 365), `response_format` (all optional except `projectId` and `url`) ### Shopping visibility > [!NOTE] > > Requires a plan with **Shopping Analytics** — every Brand and Agency plan, plus legacy > Professional and Scale; Free and Starter don't include it. On the default `summary` view, > `get_shopping_visibility` returns `available: false` (not an error) when the project simply has no > shopping/product data yet — that's a data-presence state, not a plan or connection problem, and > needs no setup step; there the plan gate only surfaces once there's data to fetch. The > `timeseries` view and a `productId` lookup check the plan first, and with no data return an empty > series or `not_found` rather than `available: false`. > **get\_shopping\_visibility** > > AI shopping/product visibility (Peec-parity) — products surfaced in AI shopping/product-carousel responses. `view="summary"` (default): ranked product list (rank, mentions, dominant platform) plus an activation-rate summary. `view="timeseries"`: daily activation-rate/product-count trend. Pass `productId` (the `id` from a summary row — the product's title) for full detail on one product instead: occurrences, vendor/pricing rows, a real per-platform breakdown, and up to 50 recent responses that surfaced it — `productId` overrides `view`. The `summary` view accepts the brand / topic / location filters and echoes `appliedFilters` + `availableFilters`. An interactive Shopping Shelf card also renders automatically beside the answer on the summary view for Apps-capable clients — see below. > > **Parameters:** `projectId` (required), `days` (default 30, max 365), `platform` (comma-separated; every view), `productId` (optional — switches to detail view; an unknown product returns `not_found`), `view` (`"summary"` | `"timeseries"`, default summary), `limit` (summary view, default 50, max 100), `offset` (summary view), `topicId`, `unbranded`, `branded`, `country`, `locationId` (the brand / topic / location filters apply to the summary view only), `response_format`, `render` (all optional except `projectId`) ### AI ads > [!NOTE] > > Ad data appears once tracked prompts surface sponsored ad units. An empty result means either that > nothing has been captured yet **or** that a filter matched nothing. `topicId` and `locationId` > each take one ID — a name, a list, or anything else that isn't a well-formed ID is refused with > `invalid_params`, while a well-formed ID that matches nothing returns an empty result (`promptId` > also takes the prompt's text, and text matching no prompt returns `not_found` rather than an empty > result). `platform` takes a comma-separated list: a mixed list silently returns just the > recognized platforms, and a list with none recognized matches nothing. Retry without filters > before reporting that a project has no ad data, and state that the retried figures are unfiltered. > `adCount`/`appearances` count ads we scraped in AI answers, not ad-platform impressions, and > commercial ad libraries don't publish spend, so there are no cost or impression figures. > **get\_ads** > > Sponsored ads surfaced in AI answers. `view="advertisers"` (default): who is running ads, ranked by ad volume with share % and a competitor flag, plus your own brand's ad share-of-voice and rank. `view="creatives"`: the individual ad creatives (advertiser, headline, appearances, prompts) — narrow with `advertiserKey`, `promptId` (a prompt id or the prompt's text), or `search`. `view="prompts"`: which tracked prompts surface the most ads, with ad coverage %. `view="timeseries"`: per-advertiser ad-frequency trend. > > **Parameters:** `projectId` (required), `view` (`"advertisers"` | `"creatives"` | `"prompts"` | `"timeseries"`, default advertisers), `days` (default 30, max 365), `filter` (`"all"` | `"competitors"`, view=advertisers), `advertiserKey` (view=creatives/timeseries), `promptId` + `search` (view=creatives), `sortBy` (`promptText`|`adUnits`|`coveragePct`|`engines`|`totalResponses`, default adUnits) + `sortDir` (view=prompts), `limit` (advertisers default 20/max 100, creatives 25/200, prompts 50/500), `platform`, `topicId`, `locationId`, `unbranded`, `branded`, `response_format` (all optional except `projectId`) ### Write actions > [!NOTE] > > `trigger_audit` and `refresh_sitemap` require a grant/key with the **write** scope; > `generate_report` works on any connection. `generate_report` and `refresh_sitemap` don't execute > without `confirm: true` — omit it and the tool returns a dry-run preview (what would happen; for > `generate_report`, whether your plan allows sharing and white-label) with no side effect. > `trigger_audit` starts straight away; pass `dryRun: true` to preview it first (page count and > audit quota). > **generate\_report** > > Generate + publish a shareable report and return its public URL. `reportType`=`sentiment`|`visibility`|`combined` (default sentiment). Works on a read-only connection, as sharing does in the app; the workspace's sharing policy and a plan with report sharing still apply. Note the `Only` suffix on the brand filters here — `unbrandedOnly`/`brandedOnly` — unlike every read tool's `unbranded`/`branded`. > > **Parameters:** `projectId` (required), `reportType`, `timeRange` (`"7d"`|`"30d"`|`"90d"`|`"365d"`, default 30d), `platforms`, `topicIds`, `locationIds`, `unbrandedOnly`, `brandedOnly`, `title`, `status` (`"published"`|`"draft"`, default published), `whiteLabel` (requires a white-label-entitled plan), `confirm` (all optional except `projectId`) > **trigger\_audit** > > Start a technical + AEO site audit for the project's pages (all tracked pages by default, or the given `pageIds`); returns the job id. Results land on `get_site_health` (`view="audits"`). Needs write access to the project, as starting an audit in the app does (a workspace viewer gets `forbidden`). Consumes the monthly audit quota (one unit per page) and is capped per plan in pages per audit — over the cap returns `bulk_limit_exceeded`, so pass a smaller `pageIds` list. Unknown `pageIds` are skipped (`no_pages` when none remain). Pages already being audited are skipped too and counted in `skippedInFlightCount`; when every page is already being audited, nothing starts and it returns `alreadyInProgress: true` with `pageCount: 0` and no job id. If another audit of the project is starting at that moment, it waits briefly, then returns an `audit_lock_busy` error and starts nothing; retry after a few seconds. The plan cap counts every requested page; the quota counts only the pages it starts. Blocked for pitch projects; an archived project returns `project_not_runnable`. > > **Parameters:** `projectId` (required), `pageIds` (optional — omit to audit every tracked page), `dryRun` (optional) > **refresh\_sitemap** > > Re-sync the project's configured sitemap — re-discovers it and reconciles monitored pages (add/update/remove) in the background; returns the job id. Read the result with `get_site_health` (`view="pages"`). Needs write access to the project (a workspace viewer gets `forbidden`). Blocked for pitch projects; an archived project returns `project_not_runnable`. > > **Parameters:** `projectId` (required), `confirm` (optional) ### Project and prompt management > [!NOTE] > > Every write tool in this section needs a connection minted with the specific capability named on > its card — see the note under [Available tools](#available-tools) above. `create_project` needs > `confirm: true`: without it the call returns a preview of what would happen (and current quota > state) and changes nothing. Every other write here acts straight away, as the app's own controls > do; pass `dryRun: true` to preview one first. > **create\_project** > > Create a project and set it up as the app's Add project flow does: the project, its > competitors, brand profile, default location and onboarding record. It counts toward the > workspace's project limit. The assistant interviews you first, one question at a time, each > with an answer it suggests from the brand's website or a web search: the brand name and > summary, where the brand sells, its competitors (list your own or pick from its suggestions), > other names it goes by, and whether to generate prompts. Say "use your suggestions" to skip > the rest. The preview lists everything that will be saved. `generatePrompts: true` writes up > to 50 tracked prompts in the background, as the app does, within your prompt allowance. > Needs `project:provision`. A connection scoped to specific project(s) can't use this tool even > with that capability — creating a project is a workspace-level action outside what a > project-scoped grant or key covers — and is refused with `forbidden`. > > **Parameters:** `workspaceId` (from `whoami`'s `workspaces`, one with `canCreateProjects: true`), `domain`, `brandName`, `brandSummary`, `reach` (`global`, `national-global`, `nationwide`, `regional` or `local`), `country` (ISO code, e.g. `GB`), `competitors` (1–20 website domains, or `{ domain, name }` entries), `generatePrompts` (all required), `city`, `language`, `brandNameDisambiguation`, `brandAliases`, `name` (defaults to the brand name), `description`, `confirm` > **archive\_project / reactivate\_project** > > Archive or reactivate a project, as the app's Archive and Reactivate actions do. An archived > project stops running its scheduled checks and keeps its data; `list_projects` marks it > "(archived)". A workspace keeps at least one active project, so archiving its only one is > refused with `last_domain`, and a pitch is refused with `pitch_not_supported`. Both need > `project:manage` and write access to the project (a workspace viewer gets `forbidden`). > > **Parameters:** `projectId` (required), `expectedRevision` (fails with `stale_revision` if the project has changed since), `dryRun` > **add\_prompts** > > Add up to 10 prompts in one call. They're **tracked** straight away, as when you add prompts in > the app: the project's next scheduled run checks them, and they count toward your prompt > allowance. The whole batch is checked against the allowance first; if it doesn't fit, nothing > is added (`quota_exceeded`), so archive some prompts first or add fewer. The prompts are then > saved one at a time, so a later prompt that fails (for example on a topic id that isn't one of > the project's) leaves the earlier ones added. Needs `prompt:draft`. > > **Parameters:** `projectId` (required), `prompts` (required — array of `{ text, intentCategory?, isBranded?, topicIds? }`, 1–10; `text` up to 2,000 characters, `intentCategory` one of `informational`, `commercial`, `transactional`, `navigational`, `post_purchase`, `topicIds` up to 20 existing topic ids), `dryRun` > **update\_prompt** > > Edit a prompt's wording, intent, branded flag, or keyword metrics. Does not archive or restore > it — use `archive_prompt` / `restore_prompt` for that. Needs `prompt:draft`. Pass > `expectedRevision` to fail with `stale_revision` instead of overwriting a concurrent edit; the > REST endpoint behind this tool accepts the same guard as an `If-Match` header instead. > > **Parameters:** `projectId`, `promptId` (both required), `text`, `intentCategory`, `isBranded`, `keywordSearchVolume`, `keywordDifficulty`, `expectedRevision`, `dryRun` > **update\_prompts** > > Bulk-edit existing prompts in place: read them with `list_prompts` (each row carries its `id` > and `currentRevision`), change what you need, and send up to 500 rows back. Each row names a > prompt by `promptId` and gives the end state for any of `text`, `isBranded`, `intentCategory`, > `topics` and `locations`; a field you leave out is left alone, and a row that changes nothing > writes nothing. A reworded prompt keeps its id and past answers, and its old wording stays as a > revision. Pass each prompt's `expectedRevision` so one edited since you read it comes back as > `conflict` instead of being overwritten. A dry run plans the edit but does not check the prompt > allowance; adding locations to tracked prompts can still be refused with `quota_exceeded` when > you apply. Result entries carry each prompt id trimmed and lower-cased, so match ids > case-insensitively. Needs `prompt:draft`. > > **Parameters:** `projectId`, `prompts` (both required — array of `{ promptId, expectedRevision?, text?, isBranded?, intentCategory?, topics?, locations? }`, 1–500; `locations` items are `{ country, city?, state? }`, at most 5 per prompt), `dryRun` > **archive\_prompt / restore\_prompt** > > Archive or restore a prompt, as the app's Archive and Restore actions do. `archive_prompt` > stops tracking it; its history and metrics are kept. `restore_prompt` tracks it again, within > your prompt allowance. Both need `prompt:activate` — a **different** capability from > `prompt:draft`, so a connection that can only add and edit prompts can't archive or restore > existing ones. > > **Parameters:** `projectId`, `promptId` (both required), `expectedRevision`, `dryRun` > **restore\_prompt\_wording** > > Restore a prompt's wording to an earlier revision by appending a **new** revision carrying that > text — history is never rewritten in place, so restoring revision 1 after edits to 2 and 3 > produces revision 4. Only wording changes; tracking, topics, and locations are untouched. Needs > `prompt:draft`; `expectedRevision` is required, not optional. > > **Parameters:** `projectId`, `promptId`, `revision`, `expectedRevision` (all required), `dryRun` > **get\_prompt\_revisions** > > A prompt's wording history, newest first, with its current revision number. Bare read — no > capability needed, and unaffected by the pitch freeze below (reads are never blocked, only > mutations are). > > **Parameters:** `projectId`, `promptId` (both required), `limit` (default 50, max 200), `beforeRevision` > [!NOTE] > > **An inactive pitch project** (declined / expired / archived) refuses every prompt write above and > every brand-profile, competitor, and topic write below with `INACTIVE_PITCH_PROMPT_MUTATION` (HTTP > 409 over REST) — it's a frozen sales-review snapshot, not a live project. > `update_opportunity_status` is different: an **active** pitch has no opportunities feature at all > (`pitch_not_supported`; 403 over REST), matching `get_opportunities`. All of them also enforce > your own role in the workspace — a connection without the tool's capability gets `missing_scope`, > and one belonging to a workspace Viewer gets `forbidden` rather than a silent no-op; > `add_competitors` and `update_competitor` additionally require a **manage**-tier role (Owner/Super > Admin/Admin), matching the in-app Competitors tab exactly. `update_brand_profile`, like the in-app > Knowledge Base, is open to Members on the project as well. > **update\_brand\_profile** > > Update the project's knowledge-base brand fields — name, headline (up to 120 characters), > positioning (the app's Brand Context), description, category, influence (the reach: `global`, > `national-global`, `nationwide`, `regional` or `local`), language (a code the app offers, e.g. > `en-GB`), website URL, case sensitivity (the app's Exact match only), disambiguation text, and > topics (the brand's products or services as short labels — not the prompt topics > `create_topic` makes). Only the fields you pass change; `topics` replaces the whole list. Does > not touch connected entities, E-E-A-T data, or the default location. Needs `knowledge:manage` > (a key scope name, not a role: Members on the project can use it too). > > **Parameters:** `projectId` (required), then any of `name` (up to 200 characters), `headline` (up to 120), `positioning` (up to 5,000), `description` (up to 5,000), `category` (up to 200), `influence`, `language`, `websiteUrl` (up to 2,000), `caseSensitive`, `brandNameDisambiguation` (up to 2,000), `topics` (up to 50 labels), plus `dryRun` > **add\_competitors** > > Add 1–20 competitors, each by website domain and optionally named, to the project's tracked set. With > `source: "manual"` and a single entry it also runs the duplicate check the in-app "add > competitor" dialog runs, refusing an existing domain, name, or name variation with `conflict`. > An entry matching an already-tracked competitor isn't added twice — that competitor is updated > instead — and the project's own domain is skipped. Needs `competitor:match`. > > **Parameters:** `projectId` (required), `competitors` (required — 1–20 entries, each a website domain such as `example.com` or `{domain, name}`; a company name alone is refused with `invalid_params`), `source` (`"manual"` or `"ai_discovery"`; omitted acts as `ai_discovery`), `dryRun` > **update\_competitor** > > Edit a tracked competitor as the in-app competitor sheet does: rename it, change its website > domain, make it a direct or a SERP competitor (`isDirectCompetitor`), and add or remove the > other names it goes by (name variations), which is how its mentions are recognised in AI > answers. Changing the domain, or making it direct, re-matches its past mentions in the > background. Everything is checked before anything is written: a variation that is another > competitor's name is refused with `conflict` (merge the two in the app instead), and removing a > name it doesn't have, adding and removing the same name, or a `domain` that isn't a website > domain is refused with `invalid_argument`. A name confirmed on another competitor moves to this > one. The result lists its name variations, so a dry run with no fields shows them. Needs > `competitor:match`. > > **Parameters:** `projectId`, `competitorId` (both required — the `id` from `get_competitors`), `name`, `domain`, `isDirectCompetitor`, `addVariations`, `removeVariations` (up to 20 names each), `dryRun` > **create\_topic** > > Create a user-authored prompt topic — the same row the in-app Topics tab creates. Needs > `prompt:draft` (topics organize prompts, so they share that surface's capability). A name the > project already uses (ignoring case; archived topics excepted) is refused with `conflict`, dry > run included, and the error carries the existing topic's id as `existingTopicId`. > > **Parameters:** `projectId`, `name` (both required; `name` up to 200 characters), `description` (up to 2,000), `seedKeyword` (up to 200), `category` (`"blind_spot"`, `"opportunity"` or `"strong"`), `dryRun` > **update\_topic** > > Edit a prompt topic as the in-app Topics tab does: rename it, change its seed keyword, archive > it (`status: "archived"`), or restore an archived one (`status: "active"`). Archiving takes the > topic off every prompt it was on, and restoring it later does not put it back on them. A name > another live topic uses is refused with `conflict` and that topic's `existingTopicId`, dry run > included — as is restoring a topic whose name a live topic has taken since. A call with none of > `name`, `seedKeyword`, or `status` returns `invalid_params`. Needs `prompt:draft`. > > **Parameters:** `projectId`, `topicId` (both required — the `id` from `get_topic_analysis`), `name`, `seedKeyword`, `status` (`"active"` or `"archived"`), `dryRun` > **update\_opportunity\_status** > > Mark an opportunity `completed` or `dismissed`; a dismissal can carry a reason and feedback text, > which are ignored when completing. Needs `opportunity:manage`. > > **Parameters:** `projectId`, `opportunityId`, `status` (all required), `dismissedReason`, `feedbackText` (dismissals only, up to 2,000 characters each), `dryRun` ### Prompt Universe (lightweight) > [!NOTE] > > This is the cheap, deterministic half of Prompt Universe — the buyer-question catalogue and code > gates, not the full research engine. **You write the actual prompt text**; these tools plan it, > then check and persist what you wrote. Both additionally need Prompt Universe to be available to > you and to the project's workspace — an unavailable tenancy answers the same `not_found` as a > project you can't see, so a connection can't tell "not offered here" from "doesn't exist" by > comparing responses. > **get\_prompt\_brief** > > The buyer question types that matter at each funnel moment (first look, compare, buy, own) for > a business model, which are decisive, and how many prompts each moment and kind should carry. > Planning input only — calls no model, creates nothing. Bare read. > > **Parameters:** `projectId` (required), `businessModelId` (e.g. `1.2`; omit it and every question type is treated as optional), `stages` (any of `tofu`, `mofu`, `bofu`, `post_purchase`; default all four), `count` (default and max 500, capped by the workspace's remaining prompt allowance) > **import\_prompts** > > Add the prompts you wrote as **tracked** prompts, the same way `add_prompts` does: the next > scheduled run checks them, within your prompt allowance. Starts no Prompt Universe run. Every > candidate is held to the Prompt Universe code gates before anything is written (no em/en dash, > no semicolon, the market's spelling, no service request at the owning moment, no near-duplicate > of each other or of the project's existing prompts), and one refusal refuses the whole batch > with `invalid_params`, naming each refused candidate and why. The whole batch is then checked > against the allowance (if it doesn't fit, nothing is added: `quota_exceeded`); `dryRun: true` > runs both checks without adding anything. The writes themselves are **not** atomic: each > prompt commits on its own and re-checks your prompt quota, so a batch can still stop partway if > another write takes the room. Once writing starts, the response's `outcomes` array says which > candidates actually landed (`created`, `failed`, or `not_attempted`) — retry only those that > aren't `created`. Needs `prompt:draft`. > > **Parameters:** `projectId`, `candidates` (both required — 1–10 entries, each `{ text, stage, kind?, branded?, topicIds? }`: `text` up to 2,000 characters, `stage` one of `tofu`, `mofu`, `bofu`, `post_purchase`, `kind` one of `open`, `sentiment`, `competitor`, `topicIds` up to 20 existing topic ids), `market` (`US`, the default, or `GB`; any other value skips the spelling check), `dryRun` ### Removed tool names Earlier iterations of this MCP shipped 35 individual tools. When those were consolidated, 26 of the old names stayed callable as deprecated aliases. **Those aliases were removed in August 2026** — calling one of the names below now returns an unknown-tool error. Use the replacement. | Removed tool | Use instead | | - | - | | `get_visibility_summary` | `get_visibility` (`group_by: "summary"`) | | `get_visibility_details` | `get_visibility` (`group_by: "platform"`) | | `get_visibility_by_topic` | `get_topic_analysis` | | `get_visibility_by_prompt` | `get_visibility` (`group_by: "prompt"`) | | `get_visibility_by_location` | `get_visibility` (`group_by: "location"`) | | `get_prompt_topics` | `get_topic_analysis` | | `get_sources` | `search_sources` | | `get_source_domain` | `get_source_detail` (`level: "domain"`) | | `get_source_domain_urls` | `get_source_detail` (`level: "domain", include: "urls"`) | | `get_source_domain_responses` | `get_source_detail` (`level: "domain", include: "responses"`) | | `get_source_urls` | `get_source_detail` (`level: "url"`) | | `get_source_page` | `get_source_detail` (`level: "url"`, with `url`) | | `get_source_page_content` | `get_source_detail` (`level: "url", include: "content"`) | | `get_source_content_types` | `get_source_trends` (`breakdown: "content-types"`) | | `get_source_types` | `get_source_trends` (`breakdown: "source-types"`) | | `get_source_article_types` | `get_source_trends` (`breakdown: "article-types"`) | | `get_source_trend` | `get_source_trends` (`breakdown: "trend"`) | | `get_prompt_query_fanout` | `get_query_fanout` (use `prompt_id`, not `promptId`) | | `get_ga4_ai_referrals` | `get_ga4_traffic` (`report: "ai_referrals"`) | | `get_ga4_traffic_trend` | `get_ga4_traffic` (`report: "trend"`) | | `get_ga4_top_pages` | `get_ga4_traffic` (`report: "top_pages"`) | | `get_ga4_traffic_sources` | `get_ga4_traffic` (`report: "sources"`) | | `get_website_issues` | `get_site_health` (`view: "issues"`) | | `get_page_audits` | `get_site_health` (`view: "audits"`) | | `get_monitored_pages` | `get_site_health` (`view: "pages"`) | | `get_issue_details` | `get_site_health` (`view: "issue_details"`) | Nine other pre-consolidation names kept their exact original name as a primary tool at the time. Six still do — `list_projects`, `list_articles`, `get_article`, `get_opportunities`, `get_sentiment`, and `get_query_fanout`; the other three (`get_visibility_history`, `get_sentiment_history`, `get_sentiment_competitors`) were folded into selector views in August 2026 and are now deprecated names — see the next section. > [!NOTE] > > The four `get_source_*` breakdown rows above are the one case where the removed name did something > no MCP tool now does: `breakdown` (and `get_source_trend`'s `group`) were arguments only those > aliases accepted. `get_source_trends` returns the top-source-domain trend directly and has no > `breakdown` argument. For the content-type, source-type, or article-type breakdown, use the [REST > endpoint](/advanced/api-usage). ### Deprecated tool names The August 2026 family-grid consolidation folded four reads into selector parameters on their family's primary tool. Unlike the removed aliases above, these four names **still work** — same handler, byte-identical payload — so existing scripts and scheduled jobs keep running. They are scheduled for removal **after 2026-10-15** (the same \~60-day window the removed aliases got); move to the replacement call before then. The REST endpoints behind these reads (`GET /visibility/history`, `GET /sentiment/history`, `GET /sentiment/competitors`, `GET /domain-authority`) are **not** deprecated — the [12-month REST deprecation policy](/changelog#deprecation-policy) governs those, and nothing REST-side is scheduled for removal. | Deprecated name | Use instead | | - | - | | `get_visibility_history` | `get_visibility` (`group_by: "date"`) | | `get_sentiment_history` | `get_sentiment` (`view: "history"`) | | `get_sentiment_competitors` | `get_sentiment` (`view: "competitors"`) | | `get_domain_authority` | `get_brand_profile` (`include: "domain_authority"`) | ## Interactive cards > [!NOTE] > > Nine tools render an interactive card beside their answer in **MCP-Apps-capable hosts** (Claude > web/desktop, ChatGPT, Goose, VS Code — all implement the same standard): > > | Tool | Card | What it shows | > | - | - | - | > | `get_visibility` | **Visibility Snapshot** | Score, trend, and per-platform bars, with brand/topic/location controls in fullscreen | > | `get_share_of_voice` | **Share of Voice** | Your own share as a headline number, its movement, and bars showing where the rest of the share sits | > | `get_competitors` | **Competitor Ranking** | List call → brand + competitors in one ranked table; `competitorId` call → the head-to-head split comparison | > | `get_sentiment` | **Sentiment Profile** | Model selector across engines, the selected engine's brand-perception write-up, and its positive/neutral/negative split | > | `get_topic_analysis` | **Topic Analysis** | A visibility bar per topic, expandable to that topic's strengths, gaps, and top rival | > | `get_source_trends` | **Sources** | Top-domains citation-trend chart, expanding to the cited-URLs table | > | `get_site_health` | **Site Audit** | Server-paginated page inventory with search, per-page **Audit** and **Issues** buttons, and a **Sync website** button, or ranked findings | > | `list_prompts` | **Prompts** | Compact prompt table inline; full metrics table with topic/branded filters and sortable columns in fullscreen | > | `get_shopping_visibility` | **Shopping Shelf** | Product image, title, price, rating, and platform (summary view only) | > > A card hides itself on a call whose answer it can't draw honestly: `get_visibility` on a > `platform`-filtered call and on `group_by=prompt|location|date`, `get_share_of_voice` on > `group_by=date`, `get_sentiment` on `view=history|competitors`, `get_topic_analysis` on > `view=prompts|heatmap`, and `list_prompts` on `status="untracked"|"suggested"`. Each of these nine > tools also takes an optional `render` boolean: pass `false` when the assistant only needs the > numbers (to summarize or feed into another call) and no card should be drawn; omitting it draws the > card, as before. Hosts without MCP Apps support just see the normal text/JSON answer. Cards are > dark/light aware and have an Expand button for a fullscreen view. > Where a card has a period control you switch window inside the card rather than sending another > message — the card queries the tool itself and remembers each window it has already loaded, so > returning to one is instant. ## Pagination Tools that return lists are paginated so you can read complete datasets without silent truncation. The one exception is `list_articles`: a call returns at most 100 rows, and `summary.total` gives the full count. ### Per-prompt visibility `get_visibility` with `group_by: "prompt"` is fully paginated. Every response includes a `pagination` object that tells you whether more prompts exist and exactly how to fetch the next page: ```json theme={null} { "pagination": { "limit": 100, "offset": 0, "returnedCount": 100, "totalCount": 740, "hasMore": true, "nextOffset": 100 } } ``` Loop until you've read every prompt: 1. Call `get_visibility` with `group_by: "prompt"` (defaults: `limit` 100, `offset` 0). 2. While `pagination.hasMore` is `true`, call again with `offset` = `pagination.nextOffset`. 3. Stop when `pagination.hasMore` is `false`. `limit` accepts 1–500 prompts per page. The total number of prompts you can page through is unbounded — paging in fixed-size chunks scales to projects with thousands of prompts. ### Other list tools Other list-returning tools page with `limit` + `offset` too: `get_opportunities` (default 100, max 500), `search_sources` (default 20, max 1000), `get_source_detail` with `include: "responses"`, `get_query_fanout`'s project aggregate (default 50, max 50 — the `prompt_id` drill-down isn't paginated), `get_prompt_answers` (default 10, max 50 — pass the previous call's `nextOffset` back as `offset`), `list_prompts`, `get_competitors` (list view), `get_site_health` (`view=pages|issues`), and `get_shopping_visibility` (summary view). `get_source_detail`'s URL lists (`include: "urls"`, or `level: "url"` without a `url` for the flat cross-project listing) are cursor-based: pass the previous response's `nextCursor` as `cursor` until `hasMore` is `false`. ## Example prompts ### Visibility analysis ``` What's my brand's AI visibility score for the last 30 days? ``` ``` How does my brand perform on ChatGPT vs Claude vs Perplexity? ``` ``` Which topics have the weakest visibility? Show me the top 5. ``` ### Site audit ``` What critical AEO issues does my site have and how do I fix them? ``` ``` Show me the pages with the lowest AEO scores. ``` ### Content ``` List all my draft articles. ``` ``` Get the full content of my latest published article for review. ``` ## Use cases ### Automated issue fixing Pair the MCP with a coding assistant to retrieve AEO/SEO issues and apply fixes in your codebase: ``` Get all critical issues for my project and propose fixes in this repo ``` The assistant fetches issues from Searchable (missing meta descriptions, alt text, heading structure, etc.), locates the relevant files, and proposes or applies fixes. ### For developers * Query AEO/SEO issues while debugging frontend code * Check if code changes would impact page audit scores * Review article content without leaving your IDE ### For AEO/SEO teams * Pull visibility summaries during standups * Extract audit data for reports * Track issue resolution progress ### For content teams * Review article status and metadata * Pull full article content for editing * Track content coverage across topics ## Troubleshooting > **Connection failed or 403 Forbidden** > > * Confirm the server URL is exactly `https://app.searchable.com/api/mcp-server/mcp` (browser-sent `Origin` headers are validated against an allowlist — hosted connectors normally call server-side without one, so they're unaffected). > * Check your API key starts with `sea_` and is enabled for MCP in Searchable settings. > * Restart your MCP client after config changes. > **The browser consent page won't complete** > > * Make sure you're logged in to Searchable in the same browser, then retry the connection from > your client. - The authorization link is single-use and expires after a few minutes — if you left > it open, restart the connection from your client to get a fresh one. - On the consent screen, > select at least one project (or leave all selected) before clicking **Authorize**. > **Legacy API key rejected (401)** > > * Confirm the key starts with `sea_` and hasn't been revoked in **Manage Account → Integrations → > MCP → Access tokens**. - Confirm the key's workspace is on a paid Searchable plan — MCP is > unavailable on the Free plan. > **No projects returned** > > * Confirm you have projects in your Searchable account. - Check that the API key belongs to the > correct workspace. > **get\_ga4\_traffic fails with 'ga4\_not\_connected'** > > * The project has no GA4 property linked, so the tool call returns a `409` error with `code: > "ga4_not_connected"`. - Connect Google Analytics 4 under **Settings → Integrations** in your > Searchable dashboard (the error's `howToFix` field carries the direct link), then retry the tool > call. > **get\_gsc\_performance fails with 'gsc\_not\_connected'** > > * The project has no Google Search Console site linked, so the tool call returns a `409` error > with `code: "gsc_not_connected"`. - Connect Google Search Console under **Settings → > Integrations** in your Searchable dashboard (the error's `howToFix` field carries the direct > link), then retry the tool call. > **get\_ai\_traffic fails with 'traffic\_not\_connected'** > > * The project has no crawler-log, CDN, or Searchable-tracker source connected, so the tool call > returns a `409` error with `code: "traffic_not_connected"`. - Connect a source under **AI Traffic > → Setup** in your Searchable dashboard (the error's `howToFix` field carries the direct link), > then retry the tool call. > **get\_shopping\_visibility fails with 'plan\_upgrade\_required'** > > * The workspace's plan doesn't include **Shopping Analytics** (Free and Starter don't), so the > tool call returns a `403`-equivalent error with `code: "plan_upgrade_required"` — on the summary > view only once the project has shopping data to fetch. - This is different from `available: false` > — that's a normal 200 response meaning the project just has no shopping data yet, not a plan > restriction. If you're seeing `available: false` instead of this error, no upgrade is needed. - > Upgrade to a plan with Shopping Analytics in **Settings → Billing**, then retry. > **A tool returns 'rate\_limited' or 'query\_timeout'** > > Neither means you've used up a daily or monthly quota. `query_timeout` means the aggregation ran > past the server-side limit — reduce the `days` window or add filters (`platform`, `topicId`, > `country`), then retry. `rate_limited` has two sources. (1) The per-connection tool-call limit: > its error carries `retryable: true` and a message naming "tool calls per minute", and it is safe > to retry after `retryAfterSeconds`. (2) A `refresh_suggestions` call to `manage_associated_source` > (where Associated Sources is enabled) while a source scout is already running or was queued > recently. Its error has no `retryable` field. Don't retry that one automatically: read > `get_associated_sources` with `view=suggestions` and check `latestRefreshCompleted`. A new scout > is paid, so start one only after the error's `retryAfterSeconds` have passed, and with the user's > confirmation. > **A write tool returns 'missing\_scope'** > > The connection lacks the scope or capability the tool needs. `trigger_audit` and `refresh_sitemap` > need **write**; the [project and prompt management](#project-and-prompt-management) tools and > `import_prompts` need the capability named on their card, which **write** doesn't include. Over > OAuth, reconnect and turn on **Allow write access** on the consent screen — it also grants the > capabilities the client asked for. It's offered only for projects you can edit, so a workspace > viewer's connection is always read-only. An API key's scopes can't be changed after it's created: > generate a new access token with **Allow write access** on, which grants **write** and every > capability, and revoke the old one. > **Tools not appearing** > > * Restart your MCP client. > * Verify the server URL and JSON config syntax. > * Check the client's MCP logs for errors. ## Security ### Read-only by default, write tools scope-gated Every read tool is annotated `readOnlyHint: true`: it can only list and read data. `trigger_audit` and `refresh_sitemap` require a grant or key with the **write** scope — a read-only connection can't invoke them at all (the call fails with `missing_scope`). `generate_report` publishes a shareable report link and works on any connection, as sharing a report is open to every project member in the app; the workspace's sharing policy can switch it off. The write tools under [project and prompt management](#project-and-prompt-management), and `import_prompts`, each need their own named capability instead. Most write tools act straight away, as the app's own controls do; pass `dryRun: true` to preview one first. The ones where a mistake costs money, exposure or a project slot — `create_project`, `generate_report` and `refresh_sitemap`, plus `manage_associated_source` where Associated Sources is enabled — still need an explicit `confirm: true` on the call, and without it return a no-op dry-run preview. ### Token & key handling * The OAuth login flow issues your client an access token scoped to exactly the projects and read/write access you approved on the consent screen — no API key is created or stored for this path. * Every request is re-checked against the server-side authorization grant, so a revoked grant stops working immediately — the token in the client's hands can't outlive the consent it came from. * Tokens refresh silently while the connection is in use; active connections persist, and an idle connection expires about 60 days after its last use, after which you simply re-authorize. * On the legacy API-key path, the `sea_` key **is** the bearer token — treat it as a secret, scope it narrowly, and revoke it in **Manage Account → Integrations → MCP → Access tokens** if it's exposed. ### Origin validation The server validates browser `Origin` headers against an allowlist (`claude.ai`, `claude.com`, the Microsoft 365 Copilot widget origins that interactive cards render from, plus dev hosts) to prevent cross-site abuse. For an allowed origin it also answers the browser's CORS preflight and sends the matching CORS headers. Requests without an `Origin` header are allowed through — that covers native clients (Claude Desktop, Cursor, curl) **and** hosted connectors like Claude.ai and ChatGPT, which call from their servers rather than a browser. This is defense-in-depth; bearer-token auth is the primary control. ### Best practices * **Rotate keys regularly** — generate new keys periodically and revoke old ones. * **Use per-use-case keys** — separate keys for separate clients makes revocation targeted. * **Revoke unused keys** — remove keys you're no longer using from Searchable settings. ## API reference ### Server details | Property | Value | | - | - | | Endpoint | `https://app.searchable.com/api/mcp-server/mcp` | | Transport | Streamable HTTP | | Protocol | MCP 1.0 | | Authentication | OAuth 2.1 + PKCE, Client ID Metadata Documents, and DCR | ### Rate limits Searchable limits MCP tool calls to **600 per minute per API key or OAuth connection**. Every app using the same API key shares that budget, and it is separate from the REST API's 600 requests/minute. Past the limit a tool returns `rate_limited` with `retryable: true` and `retryAfterSeconds`; wait that long, then retry. Two server-side controls can make a call fail retryably: * **Tool-call limit.** Over 600 tool calls in a minute returns `rate_limited`; the error says how many seconds to wait. * **Query timeout.** Long aggregations are bounded by a server-side timeout, returning `query_timeout` with a hint to narrow the `days` window or add filters. Normal interactive usage from Claude, Cursor, or similar clients is not expected to hit either. ## Support > **[All integrations](/integrations/overview)** > > Browse the integrations directory > **[Contact support](mailto:support@searchable.com)** > > Email our team # Integrations Overview Source: https://docs.searchable.com/integrations/overview Connect Searchable with your favorite tools and platforms ## Available Integrations Searchable connects with popular platforms to enrich your data and streamline your workflow. > **[MCP (AI Assistants)](/integrations/mcp)** > > Connect to Cursor, Claude Code, and Windsurf > **[Google Search Console](/integrations/google-search-console)** > > SEO performance data and keyword insights > **[Google Analytics 4](/integrations/google-analytics-4)** > > Traffic analytics and user behavior data Searchable's MCP server also works in Slack and Microsoft Teams. See [Use Searchable in Slack and Microsoft Teams](/integrations/slack-and-teams). ## Integration Benefits ### Data Enrichment Combine Searchable's AI visibility data with: * **GSC**: See which keywords drive both traditional search and AI visibility * **GA4**: Understand user behavior and conversion from AI-referred traffic * **CMS**: Seamlessly publish optimized content ### Workflow Automation * Publish content directly from Searchable to your CMS * Sync SEO metadata automatically * Trigger audits on content updates * Automate reporting with integrated data ### Unified Analytics * Single dashboard for SEO, AEO, and analytics * Cross-channel attribution * Comprehensive performance tracking * Correlation between AI visibility and traffic ## Integration Categories ### Analytics & Performance > **Google Search Console** > > **What it does:** > > * Syncs keyword rankings and impressions > * Shows CTR and position data > * Identifies technical issues > * Tracks search performance > > **Benefits for Searchable:** > > * Correlate traditional SEO with AI visibility > * Identify high-value keywords for prompt optimization > * Track both Google and AI platform performance > * Unified keyword strategy > > **Plan Availability:** Professional, Agency, Custom > **Google Analytics 4** > > **What it does:** > > * Tracks website traffic and user behavior > * Measures conversions and goals > * Provides audience insights > * Shows traffic sources > > **Benefits for Searchable:** > > * Measure AI-referred traffic > * Calculate ROI of AI visibility efforts > * Identify high-converting AI prompts > * Track user journeys from AI platforms > > **Plan Availability:** Professional, Agency, Custom ## Setup Process Most integrations follow this flow: **1. Navigate to Integrations** Go to Settings → Integrations in your project **2. Select Integration** Click on the integration you want to add **3. Authenticate** Grant Searchable necessary permissions **4. Configure** Set sync preferences and automation rules **5. Test** Run a test sync to verify connection > [!NOTE] > > Most integrations connect in under 5 minutes. Detailed guides are available for each platform. ## Integration Comparison | Integration | Plan Required | Setup Time | Data Sync | Publishing | Best For | | - | - | - | - | - | - | | MCP (AI Assistants) | Starter+ | 2 min | Real-time | No | Developer workflows | | Google Search Console | Pro+ | 2 min | Hourly | No | SEO correlation | | Google Analytics 4 | Pro+ | 3 min | Real-time | No | Traffic analysis | ## Security & Permissions ### Data Access Searchable only requests minimum necessary permissions: * **Read access**: To retrieve data for analysis * **Write access**: Only when publishing content (you control this) * **No deletion**: We never delete your data ### Authentication All integrations use secure OAuth 2.0 or API key authentication: * Tokens encrypted at rest * Automatic token refresh * Revocable anytime * Audit logs (Custom) ### Data Privacy * Data processed in compliance with GDPR and CCPA * No data sold to third parties * Stored securely with encryption * You own all your data > [!WARNING] > > When disconnecting an integration, synced data is preserved but no new data is imported. ## Managing Integrations ### Connected Integrations View all active integrations in Settings → Integrations: * **Status**: Active, Error, Syncing * **Last Sync**: Timestamp of most recent sync * **Data Preview**: Sample of synced data * **Actions**: Disconnect, Reconnect, Configure ### Sync Settings Configure sync behavior: * **Frequency**: How often to sync data * **Scope**: What data to include/exclude * **Notifications**: Alerts for sync issues * **Automation**: Trigger actions on sync ### Troubleshooting Common integration issues: > **Connection failed** > > * Verify account permissions > * Check for expired tokens > * Try disconnecting and reconnecting > * Clear browser cache > **Data not syncing** > > * Check sync schedule > * Verify data exists in source > * Review scope settings > * Check for API rate limits > **Publishing errors** > > * Verify write permissions > * Check CMS is accessible > * Review field mapping > * Test with simpler content ## Custom Integrations Custom integrations available for Custom customers: * **Salesforce**: CRM integration for attribution * **HubSpot**: Marketing automation sync * **Zapier**: Connect to 1000+ apps * **Custom APIs**: Build proprietary integrations Contact [support@searchable.com](mailto:support@searchable.com) for Custom integrations. ## Coming Soon Integrations in development: * Shopify (E-commerce) * Squarespace (Website builder) * Ghost (Publishing platform) * Notion (Documentation) * Make.com (Automation) Vote on your favorites at features.searchable.com. ## Integration Support Need help with integrations? > **Setup Guides** > > Detailed guides for each platform > **Video Tutorials** > > Watch setup walkthroughs > **[Support](mailto:support@searchable.com)** > > Contact our integration specialists > **[API Docs](/advanced/api-usage)** > > Build custom integrations ## Next Steps > **[Google Search Console](/integrations/google-search-console)** > > Connect GSC for keyword data > **[Google Analytics 4](/integrations/google-analytics-4)** > > Track traffic and conversions # Use Searchable in Slack and Microsoft Teams Source: https://docs.searchable.com/integrations/slack-and-teams Ask Searchable from Slack and Teams through Claude Tag, Slackbot, Copilot Studio and Microsoft 365 Copilot. Searchable's MCP server works with the AI agents that live in Slack and Microsoft Teams. Your team can ask about AI visibility, competitors and citations without leaving the chat. | Route | Where it runs | Who sets it up | | - | - | - | | Claude Tag | Slack channels | Each person, for their own requests. A Claude admin, for a whole channel. | | Slackbot | Slack | A developer who can build a Slack app, and a Slack admin | | Copilot Studio | Microsoft Teams | A Copilot Studio agent maker | | Microsoft 365 Copilot | Microsoft 365 Copilot chat | A developer using the Microsoft 365 Agents Toolkit | > [!NOTE] > > Slack, Microsoft and Anthropic own these screens and change them. Each step below links the > vendor's own page, so follow that page when a menu doesn't match. ## Prerequisites * **A paid Searchable plan.** MCP isn't available on the Free plan. See the [MCP guide](/integrations/mcp). * **Access to the projects you want to ask about.** You choose which ones when you authorize a connection. * **Claude Tag:** a Claude Team or Enterprise plan, with Claude Tag [set up in your Slack workspace](https://claude.com/docs/claude-tag/admins/setup-overview) by a Claude Owner. * **Slackbot:** a developer who can create a Slack app in your workspace, and a Slack admin who can approve it. * **Copilot Studio:** permission to create and publish agents, and a Teams admin who allows Power Platform apps. * **Microsoft 365 Copilot:** Visual Studio Code with the Microsoft 365 Agents Toolkit (version 6.12.0 or later), and a Microsoft 365 account that allows custom app upload. ## Quick setup The fastest route is Claude Tag with your own Searchable connector, once Claude Tag is in your Slack workspace. **1. Connect Searchable to Claude** Add Searchable as a custom connector on your Claude account. The Claude tab in the [MCP guide](/integrations/mcp#setup) has the steps. **2. Ask in a channel** In a channel where Claude Tag is installed, send: ``` @Claude list my Searchable projects ``` **3. Allow your connector** Claude replies in the thread with a prompt that only you can see, asking whether it may use your connectors. Choose **Allow with review** to check each result before it posts, or **Allow**. **4. Read the answer** Claude posts your project list in the thread. Everyone in the channel can read it. Want the whole channel to use Searchable, or scheduled checks? Set up a [channel connection](#give-each-slack-channel-its-own-searchable-access). Using Slackbot or Teams? Follow the tab for your route below. ## Detailed configuration **Claude Tag (Slack)** Claude Tag puts Claude in your Slack channels. It is in public beta and available on Claude Team and Enterprise plans. Channel work is billed by usage to your organization, under a spend limit an Owner sets. See [Claude's overview](https://claude.com/docs/claude-tag/overview). There are two ways to give it Searchable: * **Your own connector.** This is the Quick setup above. Claude asks first, then uses your connector with your permissions and only for requests you make. A teammate's request never uses it, and neither does a routine. What Claude posts back is readable by everyone in the channel. See [personal connectors](https://claude.com/docs/claude-tag/concepts/personal-connectors). * **A channel connection.** A Claude admin attaches Searchable to a channel. Everyone there gets the same access, and routines use it. See [adding connections](https://claude.com/docs/claude-tag/admins/add-connections), and set it up in the next section. Memory belongs to the channel, not to you. Anyone in the channel can read and change it, so don't ask Claude to remember anything private there. See [memory](https://claude.com/docs/claude-tag/users/memory). **Slackbot (Slack)** Slackbot is Slack's own assistant. It reaches remote MCP servers through a Slack app. See [Slack's guide](https://docs.slack.dev/ai/slackbot-mcp-client). **1. Add Searchable to a Slack app** Someone who builds Slack apps creates one for your workspace. In its settings, open **Features → MCP Servers → Get Started** and enter the name `Searchable`, the URL `https://app.searchable.com/api/mcp-server/mcp`, and the dynamic client registration auth type, which Searchable supports. Slack adds the `mcp:connect` bot scope for you. **2. Have an admin approve it** Install the app in your workspace. Where your workspace requires approval, an admin approves it from Slack's app management page. Until then Searchable doesn't appear in Slackbot. See [admin approval](https://docs.slack.dev/ai/slackbot-mcp-client/admin-approval). **3. Connect your account** In a direct message with Slackbot, use the **Apps** button in the Slackbot toolbar to connect Searchable. Slack sends you to Searchable to sign in, choose projects, and decide on write access. Write access is off by default. **4. Approve tool calls** Slackbot asks you to approve each tool call: **Allow once**, **Always allow** or **Deny**. You can change this later in the MCP config dialog. Slack cuts off a tool that takes longer than 60 seconds, and each person can have up to 5 active MCP servers. Slack lists MCP support in Slackbot as available on all subscriptions, but Slackbot access and limits vary by subscription, and admin controls over MCP servers exist only on some plans. See [Slack's help article](https://slack.com/help/articles/52414744085139-Connect-Slackbot-to-other-apps-with-MCP) for yours. **Copilot Studio (Teams)** A Copilot Studio agent can use Searchable as an MCP tool and run in Teams. See [Microsoft's MCP guide](https://learn.microsoft.com/en-us/microsoft-copilot-studio/mcp-add-existing-server-to-agent). **1. Add Searchable as a tool** Open your agent, go to **Tools**, and select **Add a tool → New tool → Model Context Protocol**. Enter the server name `Searchable`, a short description of what it does, and the URL `https://app.searchable.com/api/mcp-server/mcp`. The agent uses the description to decide when to call the server. **2. Choose the sign-in** Select **OAuth 2.0**, then **Dynamic discovery**. Searchable supports dynamic client registration with discovery. Select **Create** and **Next**, then **Create a new connection** and **Add to agent**. If you pick **API key** instead, choose **Header** and enter `Authorization` as the header name. Each person then supplies a Searchable access token, which Searchable expects in the form `Bearer` followed by the token. **3. Publish to Teams** Publish the agent, then open **Channels → Teams and Microsoft Copilot** and select **Add channel**. Share the agent with the people who should use it, then install it in Teams. See [Microsoft's publishing guide](https://learn.microsoft.com/en-us/microsoft-copilot-studio/publication-add-bot-to-microsoft-teams). Use the agent in a one-to-one chat. Microsoft documents that in Teams group chats and channels, agents can't use knowledge sources that need each person to sign in, and that such agents are supported only in one-to-one chats. Searchable also signs each person in, so treat it the same way. Copilot Studio bills agent use in Copilot Credits. Use by people with a Microsoft 365 Copilot license is included, and everyone else draws on your organization's Copilot Credits. See [Microsoft's billing rates](https://learn.microsoft.com/en-us/microsoft-copilot-studio/requirements-messages-management). **Microsoft 365 Copilot** Microsoft 365 Copilot reaches Searchable through a declarative agent with an MCP plugin. You build it in Visual Studio Code with the Microsoft 365 Agents Toolkit. See [Microsoft's walkthrough](https://learn.microsoft.com/en-us/microsoft-365/copilot/extensibility/build-mcp-plugins). **1. Create the agent** In the Agents Toolkit, select **Create a New Agent/App → Declarative Agent → Add an Action → Start with an MCP Server**. Enter `https://app.searchable.com/api/mcp-server/mcp`. **2. Pick dynamic registration** For the authentication type, select **OAuth (with dynamic registration)**. Microsoft registers a client with Searchable at runtime, so you enter no client ID or secret. Microsoft requires the server to issue a client secret, and Searchable does. See [Microsoft's DCR page](https://learn.microsoft.com/en-us/microsoft-365/copilot/extensibility/plugin-authentication-dynamic-client-registration). **3. Provision and try it** In the **Lifecycle** pane, select **Provision**. Then open [Microsoft 365 Copilot chat](https://m365.cloud.microsoft/chat), pick your agent in the **Agents** list, and ask a Searchable question. Select the sign-in prompt when Copilot shows it. Use the Agents Toolkit for this. Microsoft says the Teams developer portal doesn't support dynamic registration yet. Searchable's interactive cards render only where Copilot supports MCP Apps. See [Microsoft's MCP Apps page](https://learn.microsoft.com/en-us/microsoft-365/copilot/extensibility/plugin-mcp-apps). ## Give each Slack channel its own Searchable access When a Claude admin attaches a connection to a channel, everyone there gets the same access, whoever asks. Routines use it too. So keep it small: one project, read-only. **1. Generate a token for the channel** In Searchable, open **Manage Account → Integrations → MCP → Access tokens** and click **Generate access token**. Name it after the channel, limit it to one project, leave **Allow write access** off, and copy it. The token is then read-only, so Claude can look things up but can't change your projects. **2. Attach it to the channel** A Claude Owner or Claude Tag admin works in **Organization settings → Claude Tag**. Claude's guide to [adding a custom MCP server](https://claude.com/docs/claude-tag/admins/connections/custom) has two parts. First, a plugin whose `.mcp.json` points at `https://app.searchable.com/api/mcp-server/mcp`. Second, a custom connector with the **Bearer** credential type, your token as the key, and `app.searchable.com` as the allowed website. Put both in one [bundle](https://claude.com/docs/claude-tag/admins/add-connections#create-a-bundle) and add only that channel under **Where it applies**. **3. Test it in a new thread** In the channel, send `@Claude list the Searchable projects you can use`. You should see only that one project. Plugins apply to new threads only, so start a fresh thread. > [!WARNING] > > Anyone in the channel can ask Claude for anything the token can read, and the answers post in the > thread. A read-only token can still create a shareable report link, as any project member can in > the app. Attach it only to channels where everyone may see that project's data. ### Why a token and not a sign-in per channel Signing in to Searchable once per channel looks simpler, but it breaks. A new sign-in from the same app that narrows the projects revokes your other connections for that app, so each new channel would cut off the one before it. And if you select every project you can see, Searchable stores that as "all projects", which also covers projects you create later. A token is fixed to its project and is revoked on its own. ## Routines that check Searchable for you A routine is standing work Claude runs in a channel on a schedule. It uses that channel's connections with the same permissions as a typed request. So it needs the channel connection above. Your own connector is never used for a routine. See [Claude's routines page](https://claude.com/docs/claude-tag/users/proactivity). * **Send the message in the channel where results should post.** * **Name the timezone.** Schedules run in UTC, so say "every weekday at 9am Eastern". A routine keeps its UTC time, so its local time shifts by an hour when the clocks change. Ask Claude to reschedule it if that matters. * **Say when to stay quiet.** Tell the routine to post only when something changed. * **Anyone in the channel can manage its routines.** Send `@Claude !routines` to list them, or ask Claude to disable or move one by name. ### Weekday visibility check **Category:** Monitoring · **Uses:** `get_visibility` (`compare: "previous_period"`, `group_by: "platform"`) Posts only when your AI visibility moved by more than 5 percentage points against the period before, and names the AI platforms that moved most. On quiet days the channel hears nothing. ``` @Claude every weekday at 9am Eastern, using the Searchable MCP, compare our AI visibility for the last 7 days with the 7 days before. Post here only if it moved by more than 5 percentage points, and say which AI platforms moved most. On days it didn't, post nothing. ``` ### Weekly competitor watch **Category:** Monitoring · **Uses:** `get_competitors`, `get_share_of_voice` (`compare: "previous_period"`), `get_sentiment` Once a week, checks your tracked competitors for share-of-voice or sentiment moves worth a reaction. Weeks with nothing to report stay silent. ``` @Claude every Monday at 9am Eastern, using the Searchable MCP, check our tracked competitors against the week before. Post a short summary of any competitor whose share of voice or sentiment moved meaningfully, and anything worth a response from us. If nothing moved, post nothing. ``` ### Citation-loss check **Category:** Monitoring · **Uses:** `investigate` (`question: "citation_gap"`) Watches how often AI answers cite your site and speaks up only when that is falling. ``` @Claude every Monday at 8am Eastern, using the Searchable MCP, check whether AI answers are citing us less than before. Look at our citation trend for the last 90 days and our citation share per AI engine. Post here only if citations are falling: say which engines lost ground and which source domains AI answers cite instead. If citations held or grew, post nothing. ``` ## What doesn't work yet * **Searchable doesn't post into Slack or Teams on its own.** There are no Searchable alerts, digests or notifications in either app. An agent asks Searchable when someone asks, or when a Claude Tag routine runs. * **Teams channels and group chats.** Microsoft documents that in Teams group chats and channels, agents can't use knowledge sources that need each person to sign in, and that such agents are supported only in one-to-one chats. Searchable also signs each person in, so treat it the same way. See [Microsoft's known limitations](https://learn.microsoft.com/en-us/microsoft-copilot-studio/publication-add-bot-to-microsoft-teams#known-limitations). * **Cards depend on the host.** Searchable's interactive cards need a host that renders MCP Apps. [Microsoft documents that support](https://learn.microsoft.com/en-us/microsoft-365/copilot/extensibility/plugin-mcp-apps) for Microsoft 365 Copilot, and [Slack documents it](https://docs.slack.dev/ai/slackbot-mcp-client/returning-rich-responses) for Slackbot. Anywhere else you get the same answer as text. See [Interactive cards](/integrations/mcp#interactive-cards). * **@ChatGPT and Slack Connect.** OpenAI says @ChatGPT in Slack doesn't support Slack Connect channels shared with another organization. See [OpenAI's Slack page](https://learn.chatgpt.com/docs/third-party/slack). ## Verification and testing Ask every route the same first question: ``` List my Searchable projects ``` In Claude Tag, start with `@Claude`. You should see the projects you approved on Searchable's consent screen, or the one project on a channel's access token. Then ask a real question, such as "What's our AI visibility this week compared with last week?" Then check from Searchable's side. **Manage Account → Integrations → MCP** lists the assistants you've authorized under **Connected apps**, and your **Access tokens**. Disconnect an app or revoke a token there to cut it off. To check a routine, send `@Claude !routines` in the channel. It lists each routine with its schedule in UTC. ## Troubleshooting > **Claude says it can't reach Searchable** > > A routine or a teammate's request can't use your own connector. It needs the [channel connection](#give-each-slack-channel-its-own-searchable-access). > > If the channel connection exists, check three things: > > * `app.searchable.com` is in the connector's **Allowed websites**. Claude blocks requests to hosts you haven't allowed, and names the host in the thread. > * The credential isn't marked **Not enabled** on its connector page. If it is, select **Enable**. > * You are in a new thread. A plugin added after a thread started doesn't reach it. > **Claude Tag doesn't ask before using my connector** > > You may have saved **Allow** or **Allow with review** earlier. Change it on the **Home** tab of > the Claude app in Slack, which offers **Auto mode**, **Ask every time** and **Allow with review**. > **Searchable is missing from Slackbot** > > Where your workspace requires approval, Slackbot lists an app's MCP server only after an admin > approves the app. Also check that the app has the `mcp:connect` bot scope and lists Searchable > under its MCP servers, then reinstall the app after any change. > **Slackbot fails to load tools or times out** > > Confirm you finished the Searchable sign-in. If you disconnected the app in Searchable under **Connected apps**, connect it again. > > Slack cuts off tools after 60 seconds. For a big question, ask for a shorter period or one AI platform. > **The Copilot Studio agent says there is a billing issue** > > If it says "There is a billing issue." or "This agent is currently unavailable. It has reached its > usage limit.", your organization has used its Copilot Credit capacity. Microsoft disables custom > agents at 125% of prepaid capacity, and an admin can add capacity. Separately, as a precaution, > use the agent in one-to-one chats. See [What doesn't work yet](#what-doesnt-work-yet). > **My Microsoft 365 Copilot agent doesn't show up** > > In the Agents Toolkit **Accounts** pane, check that both **Custom App Upload Enabled** and > **Copilot Access Enabled** show under your account. If either is missing, ask your Microsoft 365 > admin. In Copilot, look for your agent's name with `dev` added at the end. > **The assistant sees fewer projects than I expected** > > What an assistant sees is set when you authorize it: the projects you ticked on Searchable's consent screen, or the one project on an access token. Ask "List my Searchable projects" to see what it has. To change it, connect again and choose the projects you want. For other errors, see [MCP troubleshooting](/integrations/mcp). More prompts to try are on the [Use cases](/use-cases) page. # Introduction Source: https://docs.searchable.com/introduction Welcome to Searchable - AI-powered SEO platform for the future of search ![Hero Light](https://mintcdn.com/searchable/eagvEWJ_NTOO1NGT/images/docs-hero-2.png?fit=max\&auto=format\&n=eagvEWJ_NTOO1NGT\&q=85\&s=eb6023a30fdb845729da4f2848dae86a) ## Welcome to the Searchable Docs! Searchable is an AI-powered SEO and AEO (Answer Engine Optimization) platform that helps businesses improve their visibility in both traditional search engines and AI-powered platforms like ChatGPT, Claude, and Perplexity. As search evolves, your strategy must too. This documentation covers everything you need to master both traditional SEO and the emerging world of AI visibility. ### What is AEO? Answer Engine Optimization (AEO) is the practice of optimizing your content to be cited and recommended by AI models. When someone asks ChatGPT "What's the best CRM for startups?", will your brand be mentioned? That's AEO. ### Platform Capabilities > **[AI Visibility Tracking](/using-searchable/visibility-tracking)** > > Track how your brand appears in answers from ChatGPT, Perplexity, Google AI Overviews, Gemini, Claude and more. The engines you track depend on your plan. > **[Searchable Agent](/advanced/ai-assistant)** > > Ask questions about your data and get recommendations in plain English > **[LLM Analytics](/setup/overview)** > > See which AI crawlers visit your site and the visitors AI sends you > **[Site Audits & Scores](/using-searchable/site-audits)** > > Comprehensive technical, content, and AEO audits with actionable recommendations > **[AI-Powered Content](/using-searchable/content-generation)** > > Generate content structured to be cited by AI and to perform in search > **[Powerful Integrations](/integrations/overview)** > > Connect Google Search Console, Google Analytics, Bing, your CMS and more ### Documentation Structure This documentation is organized to take you from beginner to expert: **Getting Started**: Set up your account, create projects, and understand the dashboard\ **Using Searchable**: Master scores, prompts, content, and audits\ **For Agencies**: Pitch prospects with their own AI visibility data and turn it into a report that wins the deal\ **Integrations**: Connect your tools and platforms\ **Recipes**: Step-by-step answers to common questions, like why your AI visibility dropped\ **LLM Analytics Setup**: Connect your hosting platform so you can see AI crawler activity on your site\ **Advanced Features**: The Searchable Agent, team collaboration, and API access\ **Changelog**: The latest updates to the Searchable API and MCP server ### Quick Links > **[Quickstart Guide](/quickstart)** > > Get up and running in 5 minutes > **[Understanding Scores](/using-searchable/understanding-scores)** > > Learn what your scores mean > **[Working with Prompts](/using-searchable/working-with-prompts)** > > Master AI visibility tracking > **[Plans & Pricing](/getting-started/plans-pricing)** > > Find the right plan for you > **[Integrations](/integrations/overview)** > > Connect your tools\\ > **[API Reference](/api-reference/introduction)** > > Build custom integrations\\ # Quickstart Source: https://docs.searchable.com/quickstart Get up and running with Searchable in under 5 minutes ## Setup your account You can set up your account by following [this link](https://app.searchable.com/onboarding). You'll choose a plan, then complete a simple onboarding form. Before your first visibility report runs, we'll show you the prompts we've suggested for your brand so you can review them. Once you confirm, your first domain is ready to go! To complete the setup quickly and easily, make sure you've got this information on hand: * Your details - name, email that you have access to * Your brand name, and any other names or spellings it goes by * Your brand's reach - global, primary market + international reach, nationwide, regional, or local * Your primary location and language * Your target website (e.g. yourwebsite.com) that you want to analyse * 3 of your main competitors (you can add up to 5) > [!TIP] > > Prompts are the questions we ask AI engines on your behalf. You can add, edit, or remove them at any time. ## Find your way around After you sign in, you'll land on the **Searchable Agent** - an AI assistant that can answer questions about your data. Try asking "What is ChatGPT getting wrong about my brand?" To make setup easy, open **Getting Started** from the progress card in your sidebar. It's a short checklist that walks you through the essentials: 1. Watch a quick overview 2. Review your visibility scores 3. Build your Knowledge Base 4. Ask the AI agent 5. Connect Google Search Console 6. Invite team members The sections below walk you through the key ones. ## See your AI visibility This is the heart of Searchable: how your brand shows up when people ask AI engines questions about your industry. We run your prompts through the AI engines on your plan on a regular schedule (daily on most plans) and show you the results in **Analytics** → **AEO Insights**. ### Key Tabs to Start With * **Visibility**: Your share of voice compared to competitors, where you rank, and your average position when you're mentioned * **Mentions & Citations**: Every time your brand was named in an AI answer, and the sources cited alongside it * **Sentiment**: How AI answers describe your brand, and where competitors are praised instead * **Sources**: Which websites AI engines rely on when answering questions in your space > [!TIP] > > Use the filters at the top to narrow by date, AI engine, topic, or competitor. The Branded / Unbranded filter shows how you perform when people don't mention you by name, which is often the more honest picture. To track more questions, go to **Prompts** → **Your Prompts**. Adding prompts uses your plan's prompt allowance. Learn more in [Working with Prompts](/using-searchable/working-with-prompts). > **[View AEO Insights](https://app.searchable.com/ai-search-insights)** > > See how your brand shows up in AI answers ## Set up your Knowledge Base Knowledge Base is where you tell Searchable who your brand is, who you compete with, and how you want your content to sound. It feeds your visibility reports, your prompts, and the Searchable Agent, so it's worth a few minutes early on. Open it from **Knowledge Base** at the bottom of your sidebar. ### Brand Profile Your brand's identity. Click **Auto-detect** and we'll draft a description of your brand from your website, which you can then edit. > [!TIP] > > Add any other names your brand goes by under **Brand naming**. If AI engines refer to you by a shorter name or a different spelling and it isn't listed, those mentions may not be counted. ### Competitors The brands you're measured against in AI search. We will automatically suggest some, and also surface companies that appear alongside you in AI answers. You can always amend the list. ### Topics Topics group your prompts, so you can filter your analytics by the areas that matter most to your business. ### Tone / Writing Style Your brand's voice is unique, so let's make sure that is reflected in your content. Set the voice, formatting, and phrases to avoid. ### Memory Long-term context about your brand and strategy that the Searchable Agent reads before every response. ### E-E-A-T Authors, credentials, and trust signals that power schema markup and author attribution in your content. > [!WARNING] > > Always double-check generated content before publishing. AI can make mistakes. ## Run your first audit Searchable's audit system provides comprehensive analysis of your site's SEO and AEO performance. As soon as you set up your domain, Searchable will automatically fetch the pages of your website using your sitemap. You'll be able to see the full list of URLs in the **On Page** → **Site Health** section of the platform. To run an audit, select the pages you want, then click the **Audit** button. Before anything runs, you'll see a confirmation showing how many pages will be audited and how many audit credits you have left. Each page uses one audit credit from your monthly allowance. > [!TIP] > > Connecting your Google Analytics 4 will allow you to sort by Visitors and audit your highest-traffic pages first. ### Site Audit Features > **Technical SEO** > > Checks meta tags, headings, canonical tags, structured data, HTTPS, broken links, social tags, and accessibility > **Page Speed** > > Page speed based on real visitor data from Chrome, plus page size > **Content Quality** > > Checks length, structure, TL;DR summaries, authorship, dates, FAQs, and trust signals > **AEO Readiness** > > Checks how ready your pages are to be found and cited by AI engines ### Understanding Your Results Your audit results are organized into several key areas: * **Site Health Score**: An overall score for your site, with separate **Technical** and **AEO** scores * **Issues**: Every problem found across your site, grouped into Technical, Content, and AEO tabs and ranked by severity. Find them in **On Page** → **Issues** * **Page Details**: Open any page for a checklist of fixes. You can ask the Agent to explain an issue, or copy a ready-made fix to hand to your developer > [!TIP] > > Start with fixing critical issues first, as they have the biggest impact on your overall performance. ## Set up integrations Connect Searchable with your existing tools and platforms to streamline your workflow. ### Popular Integrations > **[Google Search Console](/integrations/google-search-console)** > > Connect GSC for keyword data and SEO insights > **[Google Analytics 4](/integrations/google-analytics-4)** > > Track traffic from AI platforms > **[View All](/integrations/overview)** > > See all available integrations ### Setting up your first integration 1. Open the user menu at the bottom of your sidebar, select **All settings**, then go to **Integrations** 2. Choose your platform from the available options 3. Follow the platform-specific setup guide 4. Authenticate and grant necessary permissions > [!TIP] > > Start with Google Search Console and Google Analytics 4 for the richest data insights. You can also add them in your **Analytics** → **Traffic** tab. ## Generate your first content Use Searchable's AI content writer to create articles, blog posts, and more. Open it from **Actions** → **Content**. ### Content Generation Process **1. Choose how to start** Start from a blank draft (New content), a guided planning chat (Plan a content piece), or improve something you already have by uploading a file, pasting a URL, or pasting text (Optimize content) **2. Add context** Let the Agent know what kind of post you'd like to write. This can be as simple or detailed as you'd like. **3. Review the plan** If you're planning a piece, the Agent proposes an outline for you to approve before it starts writing. Feel free to add, delete, or tweak any of the sections. **4. Content generation** The Agent drafts your content section by section, and you can steer it in the chat alongside **5. Check your AI-readiness score** The SEO panel scores how likely your content is to be cited by AI, with suggested fixes **6. Publish** Export to WordPress, Webflow, or Shopify, or link a page that's already live so you can track its performance Content types include articles, blog posts, LinkedIn posts, YouTube descriptions, marketing copy, and product releases. Each piece uses your monthly article allowance. ## Next Steps Now that you're set up, explore these guides to master Searchable: > **[Understanding Scores](/using-searchable/understanding-scores)** > > Learn what your Technical and AEO scores mean > **[Working with Prompts](/using-searchable/working-with-prompts)** > > Master AI visibility tracking with effective prompts > **[Content Generation](/using-searchable/content-generation)** > > Create AI-optimized content that gets citations > **[Team Collaboration](/advanced/teams-collaboration)** > > Invite team members and collaborate effectively ### Additional Resources * **[Plans & Pricing](/getting-started/plans-pricing)**: Upgrade for advanced features * **[Integrations Guide](/integrations/overview)**: Connect all your tools * **[Troubleshooting](/using-searchable/troubleshooting)**: Solutions to common issues > [!TIP] > > Join our [Slack community](https://join.slack.com/t/searchablecommunity/shared_invite/zt-3d72toig3-5CNPzd1ujFQu_IanWGTHDA) to connect with other users and get tips from SEO experts. # Recipe: Why aren't we being cited? Source: https://docs.searchable.com/recipes/citation-gap Find the sources AI answers actually draw on for your space, and whether the gap is your content or your absence from those sources. ## The one-call version ``` investigate(projectId, question="citation_gap", days=30) ``` Returns your citation trend, the domains AI answers cite in your space, and your citation share per engine. > [!NOTE] > > Being *mentioned* and being *cited* are different, and the fix is different too. A mention gap is > a content problem. A citation gap is usually a **third-party source** problem — you are absent > from the pages the models actually read. ## The chain, step by step ### 1. Is the gap real, and getting worse? ``` investigate(projectId, question="citation_gap", days=90) ``` Read `brandCitationTrend` — your own citation volume over time. A decline there with flat visibility means you are still being talked about, but no longer used as the source. > [!NOTE] > > `get_source_trends` does **not** answer this one. It returns the trend for the most-cited > *domains* in your space, not your own citation volume — there is no parameter to switch it. The > brand-level series is only available through `investigate`. ### 2. What do the models actually cite? ``` search_sources(projectId, days=30) ``` The domains carrying answers in your space. **This is the core of the recipe.** The gap is almost always "these sources do not mention us", not "our page is not good enough". ### 3. Which sources, specifically ``` get_source_detail(projectId, level="domain", domain="") get_source_detail(projectId, level="url", url="") ``` Down to the exact page being cited. That page — a listicle, a comparison, a review roundup — is the outreach target. ### 4. Is it engine-specific? ``` get_visibility(projectId, group_by="platform") ``` Compare citation share across engines. One engine citing you far less than its peers is that engine's sourcing behaviour, not a content deficiency. ### 5. Where you are cited but not surfaced ``` get_prompt_answers(projectId, promptId="") ``` The raw answers. Sometimes you are cited and still not recommended — a positioning problem rather than a visibility one, and it needs a different fix. ## How to read it | What you see | What it usually means | | - | - | | Top cited domains do not mention you | The most common case. This is outreach and PR work, not on-site SEO. | | You are cited, but not recommended | The model reads you and picks someone else. Look at positioning in the answer text. | | Citation share strong on one engine, weak elsewhere | Sourcing differences between engines. Usually not something to fix on-site. | | Citation volume falling, visibility flat | You are still known but no longer used as evidence. Freshness of the citing sources is often the cause. | | Few cited domains overall | A thin-source space. Owning even one or two authoritative sources can move it disproportionately. | ## What this cannot tell you Whether a cited source will accept a mention, what it costs, or whether it is worth pursuing. The data names the targets; it cannot rank them by how winnable they are. # Recipe: Where do we stand against our competitors? Source: https://docs.searchable.com/recipes/competitive-position Read share-of-voice standing with ranks and movement, then find the topics where a rival currently leads. ## The one-call version ``` investigate(projectId, question="competitive_position", days=30) ``` Returns your share and rank with period movement, the competitor table, the topics where a rival out-ranks you, and your own direction of travel — in one request. ## The chain, step by step ### 1. The standing ``` get_share_of_voice(projectId, compare="previous_period") ``` Read three things, in this order: your **rank**, your **delta**, and *who* moved. A rank held while everyone's share compressed is a different story from a rank held while one rival doubled. ### 2. Where the gap is ``` get_topic_analysis(projectId, days=30) ``` Per-topic visibility with the leading rival for each. The actionable subset is the topics where someone else currently leads — that is the shortest route to share, because the demand is already proven. ### 3. Drill into a contested topic ``` get_topic_analysis(projectId, view="prompts", topicId="") ``` Per-prompt rank inside that topic, with the competitors outranking you on each. This is the level at which you can write something. ### 4. How they are winning it ``` search_sources(projectId, topicId="") get_source_detail(projectId, level="domain", domain="") ``` If a rival leads a topic, some set of sources is carrying them. Those sources are the target — more reliably than out-writing the rival's own page. ### 5. Your own trajectory ``` get_share_of_voice(projectId, group_by="date", days=90) ``` Standing is a snapshot; this is the direction. A #3 climbing is a much better position than a #2 sliding. ## How to read it | What you see | What it usually means | | - | - | | Your share fell, absolute visibility flat | You did not get worse — someone else got better. Respond to *them*, not to your own content. | | Your share rose, absolute visibility flat | A rival lost ground. Worth understanding why before assuming it holds. | | Strong overall, weak on several topics | The usual shape. Topic gaps are where the remaining share actually is. | | A rival leads a topic you do not cover | Not a loss — an unentered market. Cheaper to win than a contested one. | | One rival gaining across every topic | A category-level move (a launch, a campaign, a partnership), not a topic problem. | ## What this cannot tell you Share of voice measures presence in AI answers, not revenue, intent quality, or whether a topic is worth winning. A topic where you rank last may be one you deliberately do not serve — the data cannot see that, and will keep reporting it as a gap. # Recipe: Why did our AI visibility drop? Source: https://docs.searchable.com/recipes/visibility-drop Confirm the drop, isolate which engines moved, separate 'we fell' from 'a rival rose', and find where the loss sits. ## The one-call version ``` investigate(projectId, question="visibility_drop", days=30) ``` That runs this whole chain server-side and returns the measurements together. Everything below is what it fetches, why, and — the part the tool deliberately leaves to you — how to read the result. > [!NOTE] > > `investigate` returns **measurements, not a cause**. It will tell you ChatGPT fell 38 points while > Gemini held; it will not tell you why, because the reason is usually something outside the data (a > rebrand, a campaign ending, a competitor launch). The interpretation rules below are how you close > that gap. ## The chain, step by step Run these individually when you want to vary the window, filter to a topic, or drill past what the composite returns. ### 1. Confirm the drop is real, and date it ``` get_visibility(projectId, group_by="date", days=90) ``` A single low day is noise; a step change is an event. What you want from this step is the **date the level changed**, because that date is what you match against your own release, campaign, and site-change history. ### 2. Isolate which engines moved ``` get_visibility(projectId, group_by="platform", compare="previous_period") ``` This is the highest-signal step. See the interpretation table below. ### 3. Separate "we fell" from "a competitor rose" ``` get_share_of_voice(projectId, compare="previous_period") ``` Visibility is absolute; share of voice is relative. They move independently, and which one moved changes the response entirely. ### 4. Locate the loss ``` get_topic_analysis(projectId, days=30) get_topic_analysis(projectId, view="prompts", topicId="") ``` Topic first, then the prompts inside the weakest topic — that's the level where you can actually act. ### 5. Check whether the sources moved under you ``` search_sources(projectId, days=30) get_source_trends(projectId, days=90) ``` If the domains AI answers cite for your space changed, your ranking may not have moved at all — the answer did. ## How to read it | What you see | What it usually means | | - | - | | One platform fell, others flat | That engine changed something — a model update or a sourcing change. Your site is probably not the variable. | | Every platform fell together | Something on your side, or a data-collection gap. Check step 1's date against your own change log first. | | Visibility flat, share of voice down | You did not lose ground; a competitor gained. Look at which one in `competitorsGaining`. | | Visibility down, share of voice flat | The whole category got quieter — often seasonality or fewer prompts firing, not a loss to a rival. | | Both down, one topic dominating the loss | A content problem localised to that topic. Step 4 tells you which prompts. | | Citation sources churned heavily | The answer moved rather than your rank. Winning the new sources matters more than editing your page. | ## What this cannot tell you The data has no view of your rebrand, your campaign calendar, a competitor's launch, or a change in how your prompts are worded. A drop that lines up exactly with one of those is explained by it — check that before treating the numbers as a content problem. # Send Akamai traffic to Searchable (DataStream 2) Source: https://docs.searchable.com/setup/akamai-datastream2 Configure an Akamai DataStream 2 stream to push request logs to Searchable over HTTPS. No code changes — all configuration is done inside Akamai Control Center. ## What this does Akamai [DataStream 2](https://techdocs.akamai.com/datastream2/docs/welcome-datastream) ships near-real-time request logs from your Akamai properties to a destination of your choosing. We point that stream at Searchable's tracker endpoint, classify the AI bots at the edge, and drop everything else. No code changes — all configuration happens inside Akamai Control Center. > [!NOTE] > > **No code changes.** All configuration is done inside Akamai Control Center. ## Prerequisites > [!TIP] > > An Akamai account with **DataStream 2** entitled on the contract that owns your delivery property > [!TIP] > > The **Edit** role on that property, or an Akamai admin who can author and activate a stream > [!TIP] > > A Searchable project with your domain confirmed > [!TIP] > > DataStream 2 is a per-contract product. If the **DataStream** entry doesn't appear in Control > Center, your Akamai contract doesn't include it yet — your Akamai account team can add it. The > legacy DataStream 1 product is not supported by this integration; you need DataStream 2 (the > JSON-format successor). ## Setup **1. Generate an integration token in Searchable** 1. Open your Searchable dashboard 2. Go to **LLM Analytics → Setup** 3. Pick **Akamai** as your crawler source 4. Click **Generate token** Copy the token now — it starts with `sa_…` and won't be shown again. You can always generate a new one if you lose it. The endpoint URL is fixed: ``` https://tracker.searchableanalytics.com/v1/akamai-logs ``` > [!TIP] > > Sanity check before pointing DataStream at the endpoint: > `curl https://tracker.searchableanalytics.com/v1/akamai-logs` should return `200 ok`. That's the same health check Control Center's **Validate and save** step runs against the URL when you finish the wizard. **2. Open the DataStream 2 wizard** In Akamai Control Center, navigate to: **CDN → DataStream → Create new stream** Give the stream a name like `Searchable LLM Analytics` and assign it to the group that contains the property fronting your domain. Pick the **property** that serves the traffic you want Searchable to see — DataStream is scoped per-property, not per-account. > [!TIP] > > If your domain is fronted by more than one property (for example, `www` and `api` on separate properties), create one stream per property and point them all at the same endpoint with the same token. Searchable tags events by host, so they still arrive split by domain in the dashboard. **3. Set the log file format to Structured JSON** Before picking fields, set the log format. On the **Data sets** step, look near the top for **Log file format** and set it to **Structured JSON**. > [!WARNING] > > **"JSON" and "Structured JSON" look almost identical in the dropdown — pick Structured JSON.** Only Structured JSON emits one record per line (NDJSON), which is what our endpoint ingests. Choosing plain **JSON** is the single most common cause of a stream that delivers successfully on Akamai's side but produces no events in Searchable. **4. Pick the data sets (log format fields)** Still on the **Data sets** step, select the fields below. Field names are case-sensitive — note `UA` is capitalised (it is **not** `reqUserAgent`), and the sub-country admin region is `state` (not `region`). Select all of these: * `reqID` * `reqMethod` * `reqHost` * `reqPath` * `UA` *(capital — not `reqUserAgent`)* * `statusCode` * `cliIP` * `reqTimeSec` * `reqEndTimeMSec` Of those, the endpoint **drops any record** missing `reqID`, `reqMethod`, `reqHost`, `reqPath`, `UA`, `statusCode`, `cliIP`, or `reqTimeSec`. `reqEndTimeMSec` is optional in the strict sense — a record without it is still ingested — but tick it anyway: it's what populates response time in your dashboard. Recommended additions (used for enrichment + debugging; missing fields don't break the integration): * `queryStr` * `referer` *(HTTP-standard single-r spelling)* * `rspContentLen` * `turnAroundTimeMSec` *(time-to-first-byte in ms)* * `cacheStatus` * `country` * `state` * `city` * `edgeIP` * `tlsVersion` The exact list, comma-separated for quick copy/paste, is also shown in the Searchable setup card. > [!NOTE] > > In Structured JSON, Akamai emits the request-ID field as `reqId` (lowercase `d`) and all numeric values as JSON strings. Searchable accepts either casing (`reqID` or `reqId`) and coerces the strings, so you don't need to do anything special on your end — just tick the field as Control Center labels it. After changing the format or the field selection, confirm your fields are still ticked and **save** — switching the log file format can reset the selection. > [!WARNING] > > `reqID` is load-bearing and required. DataStream 2 has at-least-once delivery semantics, so the same record can arrive in multiple batches; Searchable derives a stable event ID from `reqID` to deduplicate them. Records without it are dropped. > [!NOTE] > > **`reqTimeSec` vs `reqEndTimeMSec`**: despite the suffix, `reqEndTimeMSec` is a *duration* (how long the request took, in ms), not a wall-clock value. The actual epoch timestamp is `reqTimeSec` (epoch seconds). Searchable uses `reqTimeSec` for the event time and `reqEndTimeMSec` for `response_time_ms` — both fields are useful and we capture both when present. **5. Configure the HTTPS delivery** On the **Delivery** step, choose **HTTPS** and fill in: | Field | Value | | - | - | | **Endpoint URL** | `https://tracker.searchableanalytics.com/v1/akamai-logs` | | **Authentication** | None | | **Custom HTTP header — name** | `X-Searchable-Token` | | **Custom HTTP header — value** | `` *(no `Bearer ` prefix)* | | **Compress files** | Off *(see note below)* | > [!WARNING] > > **Use `X-Searchable-Token`, not `Authorization` — and do not prefix the value with `Bearer `.** Paste the raw `sa_…` token as the header value, with no prefix, no space, and no quotes. > [!TIP] > > Set **Authentication** to **None** and put the token in a **custom header**. Akamai's built-in Authentication choices (Basic, mTLS) are for upstreams that expect those formats — Searchable verifies its own signed token, so the right slot is a plain custom header. > [!NOTE] > > DataStream 2's HTTPS delivery sends batched, line-delimited JSON. Searchable's endpoint accepts both uncompressed and gzip-compressed bodies. If you turn on **Compress files**, leave the rest of the settings unchanged — Akamai sets `Content-Encoding: gzip` automatically and our endpoint decompresses on the way in. **6. Set the upload frequency** On the **Frequency** step, choose **30 seconds** (the default) or **60 seconds**. Either is fine — both keep dashboard latency under a minute. Don't choose the largest "files per group" / longest interval option. Larger Akamai batches are still well under our 5 MB payload cap on any realistic traffic level, but tighter intervals give you faster feedback in the dashboard while debugging. **7. Validate and activate the stream** On the **Review** step, click **Validate and save**. Akamai sends a `GET` to the endpoint URL and expects a `200` response — Searchable's tracker responds with a literal `ok`. If validation fails, see [Troubleshooting](#troubleshooting) below. Once validation succeeds, click **Activate**. Akamai activation typically completes within a couple of minutes on Staging and 5–10 minutes on Production. Once active, expect events in Searchable as soon as an AI bot hits your site. ## Verifying the connection In Searchable: 1. Go to **LLM Analytics → Setup** 2. Look at the Akamai card status 3. Click **Check** if it still shows "Waiting for first event" | Status | What it means | | - | - | | **Waiting for first event** | The stream is configured but no AI bot has hit your site yet. Typical wait is a few hours for sites that are already indexed. | | **Connected** | Events are arriving. The card shows the count from the last 24 hours. | You can also confirm in Akamai: **CDN → DataStream → your stream → Monitor**. The stream's metrics show outgoing batches and the HTTP response codes from our endpoint. Healthy delivery looks like a steady stream of `204` responses. ## What Searchable receives For each request that matches an AI-bot user agent, Searchable receives: * HTTP method, path, and host (query strings stripped before storage) * User agent * Referer * Country, state, and city (from `country` / `state` / `city`) * Response status and bytes out (`rspContentLen`) * Edge turnaround time (`turnAroundTimeMSec`) * Akamai edge metadata (`edgeIP`, `tlsVersion`, `cacheStatus`) — preserved as `custom_properties` for debugging * The DataStream 2 `reqID` — used as the dedup key on our side Bodies, headers other than `User-Agent` / `Referer`, cookies, and full IPs are never sent or stored. The DataStream 2 `reqID` is hashed into a deterministic event ID so retried batches collapse into a single event server-side. ## Troubleshooting > **Validation fails on 'Validate and save'** > > Akamai's validator does a `GET` against your endpoint URL and expects `200`. The two common reasons it fails: > > * **Typo in the URL** — the correct value is exactly `https://tracker.searchableanalytics.com/v1/akamai-logs`. No trailing slash, no path suffix. > * **A custom Akamai delivery network policy** is blocking egress to `tracker.searchableanalytics.com`. In strict environments this surfaces as a connection error rather than a 4xx. If you suspect this, ask your Akamai admin to allow-list that hostname for the delivery property. > > The custom header isn't validated at this step — Akamai's `Validate and save` only verifies that the URL is reachable. Auth issues surface in the **Monitor** tab after the stream activates. > **Searchable shows 401 errors or the card stays 'Not connected'** > > The token header is missing or wrong. > > * Make sure you added a **custom HTTP header** named exactly `X-Searchable-Token` > * The value must be the **raw `sa_…` token with no `Bearer ` prefix**, no quotes, and no leading or trailing space. A value of `Bearer sa_…` will fail — paste the token on its own > * Confirm the **Authentication** dropdown is set to **None** — picking **Basic** there overrides the custom header > * If you've recently revoked the token in Searchable, generate a new one and update the stream's custom header (no need to recreate the stream) > > In Akamai's **Monitor** tab, repeated `401` responses are the visible symptom — fix the header and the next batch will succeed. A `403` instead means the header arrived but the token failed signature verification: it's truncated, from a different environment, or has been revoked. > **Akamai's Monitor tab shows 4xx responses** > > Map the response code to the cause: > > * **`401 Unauthorized`** — the token header is missing or malformed; see the section above. > * **`403 Forbidden`** — the header arrived but the token failed verification (truncated, revoked, or from a different environment). > * **`413 Payload Too Large`** — the batch exceeded Searchable's 5 MB payload cap. This is rare on default frequency settings (30s / 60s); if you see it, lower the upload interval rather than raising file sizes. > > While 4xx errors are occurring, Akamai retries each batch a few times and then drops it. Fix the root cause and the stream catches up automatically with the next batch. > **Akamai's Monitor shows successful 204 responses but no events appear in Searchable** > > The endpoint silently drops records it can't parse or that are missing required fields, rather than rejecting the whole batch — so a misconfigured **Data sets** step shows up as healthy delivery on Akamai's side and empty cards on Searchable's side. Check these two things, in order: > > **1. Log file format is Structured JSON.** Open **Data sets** and check **Log file format** near the top. It must be **Structured JSON**, not **JSON** — the two look nearly identical in the dropdown, but only Structured JSON emits one record per line, which is what our endpoint parses. This is the most common cause of a stream that delivers `204`s and produces nothing. > > **2. Every required field is selected:** > > * `reqID`, `reqMethod`, `reqHost`, `reqPath`, `UA`, `statusCode`, `cliIP`, `reqTimeSec` > > Field names are case-sensitive — `UA` is capitalised and is not `reqUserAgent`. After missing `UA`, the most common single-field cause is missing `reqTimeSec`: without it the record is dropped outright. > > If the format is right and all required fields are present, the next most common cause is a domain mismatch — see "Status stays on 'Waiting for first event'" below. > **I ticked reqEndTimeMSec instead of reqTimeSec** > > These two are not interchangeable, despite the similar names: > > * **`reqTimeSec`** is the wall-clock timestamp of the request (epoch **seconds**). This is what Searchable uses to place the event in time, and it is **required** — a record without it is dropped. > * **`reqEndTimeMSec`** is a *duration* — how long the request took to process, in ms — **not** a wall-clock value despite the name. Searchable uses it for response time only. > > Tick both. If you ticked only `reqEndTimeMSec`, no events reach your dashboard at all, because every record fails the required-field check. > > Fix: open **Data sets**, tick `reqTimeSec`, and save. The stream re-activates in place and the next batch arrives correctly. > **Events look deduplicated incorrectly (many requests collapse to one row)** > > `reqID` is populated with a non-unique value, or `reqID` was omitted from the field selection altogether. > > * Open the stream's **Data sets** step and confirm `reqID` (capital `ID`) is selected — Akamai's UI lists it alphabetically near the bottom of the request-section fields > * `reqID` is Akamai's per-request unique identifier and is auto-populated; no property-level VCL or PMUSER variables required > * If `reqID` is selected and you still see this symptom, contact support and include a few sample records — most often this turns out to be a property-level override blanking the value > > Without `reqID`, Searchable falls back to a (timestamp, path, user-agent) heuristic that's much coarser. The fix is non-destructive — add `reqID` and republish. > **Timestamps look wrong (events appear in 1970, or far in the future)** > > The wall-clock field is `reqTimeSec` — epoch **seconds**, which Searchable converts to milliseconds on ingest. `reqEndTimeMSec` is a duration, not a timestamp, and is never used to place an event in time. > > If timestamps look wrong, the usual cause is a property-level override rewriting `reqTimeSec`, or a stream config copied from a much older DataStream 1 export that maps a different field into that slot. Confirm `reqTimeSec` is ticked in **Data sets**, then contact support with a few sample records if it persists. > **Status stays on 'Waiting for first event' for more than 24 hours** > > A few possible causes, in order of likelihood: > > * The stream isn't activated — open the stream in Control Center and confirm the status badge reads **Active**, not **Inactive** or **Pending activation**. Activations on Production take 5–10 minutes > * Your domain in Searchable doesn't match the host the Akamai property serves (check **LLM Analytics → Setup → Confirm your domain**) > * The Akamai property fronts a different host than the one you're testing with — DataStream is scoped per-property, so traffic going through a different property won't appear > * No AI bot has visited yet — try visiting your site with a known AI user agent (e.g. `Mozilla/5.0 (compatible; GPTBot/1.0)`) to trigger a test event > > If Akamai's **Monitor** tab shows successful `204` responses but Searchable still says no events, the issue is almost always a domain mismatch on the Searchable side. > **I want to test the stream against staging before flipping production** > > Akamai's standard staging-network workflow works fine — activate the stream on **Staging** first, then point a few test requests at your staging hostname (Akamai exposes the staging network at `.edgesuite-staging.net` or via a `Pragma: akamai-x-cache-on` header trick from your origin). Successful staging delivery is a strong signal the production activation will work as well. > > You'll see events arrive in the same Searchable project — there's no separate staging endpoint to worry about. ## Multi-site and region-level properties (domain mapping) A single Akamai property sometimes serves traffic for many different hostnames — for example, a region-level property that fronts `example.co.uk`, `example.de`, and `example.fr`. With a standard integration token those requests would all land on one Searchable project. Domain mapping lets you route each hostname to the right Searchable project from a single DataStream stream, without touching the Akamai config or reissuing the token. > [!NOTE] > > **Gated rollout.** The domain-mapping manager is available on request. If you don't see a **Domain > mappings** section on the Setup tab, reach out to support to have it enabled for your account. ### How it works 1. In Searchable, go to **LLM Analytics → Setup** and open **Domain mappings**. 2. Create a mapping and add host rules — one rule per hostname your Akamai property serves (e.g. `example.co.uk` → the UK project, `example.de` → the German project, and so on). 3. Generate a **domain-mapping token** from the same manager. The token looks the same as a standard `sa_…` integration token and goes in the same `X-Searchable-Token` header, raw and with no `Bearer ` prefix. 4. Paste the token into your DataStream's **Custom HTTP header** field (replacing any existing token). Searchable inspects the `reqHost` field of each incoming log record and routes it to the matching project. **Hostnames that have no rule are silently discarded** — they are never stored. Rule changes propagate within about a minute; you don't need to touch the Akamai stream or generate a new token. > [!WARNING] > > Any hostname your Akamai property serves that is **not** covered by a domain-mapping rule will be > dropped at ingestion and will not appear in any Searchable dashboard. Double-check your rules > cover every hostname you want tracked. ### Diagnostic events DataStream diagnostic envelopes from a mapping token are not attributable to a single project and are dropped in the current release. If you need per-batch ingestion diagnostics, use a standard (single-project) integration token instead. ## Removing the integration To stop sending traffic to Searchable: 1. Akamai → **CDN → DataStream → your stream → Deactivate** (and optionally delete the stream once it's deactivated) 2. Searchable → **LLM Analytics → Setup → Tokens** → revoke the token Both sides are independent — revoking the token alone is enough to stop ingestion immediately, even if the stream stays configured in Akamai (its deliveries will start returning `401`, which Akamai will retry and then drop). ## Next steps > **[See the data](/using-searchable/visibility-tracking)** > > Open LLM Analytics to see which assistants are crawling your site. > **[Add Search Console](/integrations/google-search-console)** > > Correlate AI crawls with search demand. # Send Cloudflare traffic to Searchable (Logpush) Source: https://docs.searchable.com/setup/cloudflare Stream Cloudflare HTTP request logs directly to Searchable using a native Logpush job — Enterprise plan only ## What this does [Cloudflare Logpush](https://developers.cloudflare.com/logs/about/) is Cloudflare's native, batched log delivery system. We point a Logpush job at Searchable's ingest endpoint, Cloudflare ships HTTP request logs to us, we classify the AI bots, and drop everything else. > [!NOTE] > > **Logpush requires the Cloudflare Enterprise plan.** If you're on a different plan, use the **[Cloudflare Worker](/setup/cloudflare-worker)** path instead — it works on every plan including Free. ## Logpush vs. Worker — which one? | | Logpush | Worker | | - | - | - | | **Plan required** | Enterprise | Any plan, including Free | | **Latency to dashboard** | \~30–60 seconds (batched) | Real-time (per-request) | | **Setup complexity** | One Logpush job, no code | Paste a script, add a route | | **Sites per zone** | All sites on the zone | Per-route | If you're on Enterprise, Logpush is the lowest-overhead option. If you'd rather avoid an Enterprise feature dependency, the Worker path works on Enterprise too. ## Prerequisites > [!TIP] > > A Cloudflare zone on the Enterprise plan with Logpush enabled > [!TIP] > > Permission to create Logpush jobs on the zone > [!TIP] > > A Searchable project with your domain confirmed ## Setup (dashboard) **1. Generate the destination URL in Searchable** 1. Open your Searchable dashboard 2. Go to **LLM Analytics → Setup** 3. Pick **Cloudflare Logpush** as your crawler source 4. Click **Generate token** Searchable returns a single ready-to-paste destination URL with the auth header pre-encoded as a query parameter. It looks like this: ``` https://tracker.searchableanalytics.com/v1/cloudflare-logs?header_Authorization=Bearer%20sa_… ``` Copy it now — the token portion won't be shown again. > [!TIP] > > Cloudflare Logpush doesn't have a separate header field — auth has to be encoded into the destination URL itself using `header_*` query parameters. Searchable handles that encoding for you. **2. Open the Logpush UI on your zone** Open [dash.cloudflare.com](https://dash.cloudflare.com/) → your zone → **Analytics & Logs → Logpush**. Click **Create a Logpush job**. **3. Configure the job** | Field | Value | | - | - | | **Dataset** | HTTP Requests | | **Destination** | HTTP destination | | **Destination URL** | the full URL you copied from Searchable (auth is already in the query string) | | **Timestamp format** | Unix | | **Fields** | see below | Pick exactly these fields: * `RayID` * `ClientRequestMethod` * `ClientRequestURI` * `ClientRequestPath` * `ClientRequestHost` * `ClientRequestScheme` * `ClientRequestUserAgent` * `ClientRequestReferer` * `ClientIP` * `ClientCountry` * `EdgeResponseStatus` * `EdgeStartTimestamp` * `OriginResponseTime` * `EdgeColoCode` * `CacheStatus` * `EdgeResponseBytes` > [!WARNING] > > The timestamp format **must be Unix**, not RFC3339. Logpush jobs default to RFC3339, and Searchable will reject batches that don't include a Unix timestamp. **4. Save and verify** Click **Save**. Cloudflare will validate the destination immediately — Searchable accepts the validation ping. Return to **LLM Analytics → Setup** in Searchable. The status strip should show **Connected** within a few minutes once an AI bot hits your site. ## Setup (API) If you'd rather create the job via the [Cloudflare API](https://developers.cloudflare.com/api/operations/logpush-jobs-create-logpush-job), here's the equivalent call. Replace `{zone_id}`, `{cf_api_token}`, and the `sa_…` placeholder with your own values. ```bash theme={null} curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/logpush/jobs" \ -H "Authorization: Bearer {cf_api_token}" \ -H "Content-Type: application/json" \ -d '{ "name": "searchable-ai-logs", "logpull_options": "fields=RayID,ClientRequestMethod,ClientRequestURI,ClientRequestPath,ClientRequestHost,ClientRequestScheme,ClientRequestUserAgent,ClientRequestReferer,ClientIP,ClientCountry,EdgeResponseStatus,EdgeStartTimestamp,OriginResponseTime,EdgeColoCode,CacheStatus,EdgeResponseBytes×tamps=unix", "destination_conf": "https://tracker.searchableanalytics.com/v1/cloudflare-logs?header_Authorization=Bearer%20sa_YOUR_INTEGRATION_TOKEN", "dataset": "http_requests", "enabled": true }' ``` ## Already have a token? Build the destination URL manually If you have an existing `sa_…` token (saved from a previous Generate flow, stored in 1Password, etc.) you can construct the destination URL yourself: ``` https://tracker.searchableanalytics.com/v1/cloudflare-logs?header_Authorization=Bearer%20 ``` Notes: * The literal space between `Bearer` and the token must be percent-encoded as `%20` * Our token alphabet (`sa_.`) is already URL-safe, so nothing else needs encoding * Cloudflare's `header_*` query-parameter convention is documented [here](https://developers.cloudflare.com/logs/get-started/enable-destinations/http/) You can also use the **"Already have a token? Build the URL"** form in Searchable's setup UI to do this for you. ## Verifying the connection Cloudflare batches Logpush deliveries every \~30–60 seconds, so events take a moment to appear after the first AI bot hit. In Searchable: 1. Go to **LLM Analytics → Setup** 2. Look at the status strip at the bottom of the page 3. Click **Check** if it still shows "Waiting for first event" You can also confirm in Cloudflare: **Analytics & Logs → Logpush → your job**. The job's metrics show successful and failed delivery counts. ## Multiple zones Each Logpush job is scoped to a single Cloudflare zone. If your domain is split across multiple zones (e.g. `example.com` and `example.io`): 1. Generate one integration token per zone, or reuse one — both work 2. Create one Logpush job per zone with the same destination URL 3. Searchable will tag events by host, so you'll still see them per-domain in the dashboard ## Troubleshooting > **Logpush job shows delivery errors** > > Open the job in Cloudflare and check the error message: > > * **`401 Unauthorized`** — the `header_Authorization` query parameter is missing or malformed. Re-copy the destination URL from Searchable, or rebuild it manually using the format above. > * **`400 Bad Request`** — most often `timestamps=unix` is missing. Edit the job and confirm timestamp format is **Unix**. > * **`Invalid destination URL`** — Cloudflare's destination validation pings the endpoint. If validation fails, double-check the host (`tracker.searchableanalytics.com`) and that the URL has the `?header_Authorization=Bearer%20…` query string attached. > **Status stays on 'Waiting for first event'** > > Logpush only delivers logs for traffic that's actually flowing through Cloudflare. Things to check: > > * The Cloudflare zone is proxied (orange cloud) for the routes AI bots are hitting, not just DNS-only (grey cloud) > * The Logpush job is **enabled** (jobs can be paused) > * Your domain in Searchable matches the zone's hostname (check **LLM Analytics → Setup → Confirm your domain**) > > If everything looks right, hit your site with a known AI user agent and wait \~60 seconds for the next Logpush batch: > > ```bash theme={null} > curl -H "User-Agent: GPTBot/1.0 (+https://openai.com/gptbot)" https://yourdomain.com/ > ``` > **I see double-counted events** > > If you also run the **[Cloudflare Worker](/setup/cloudflare-worker)** path on the same zone, both will send each AI-bot request. Searchable de-duplicates server-side using the Cloudflare Ray ID, so the dashboard shows each request once. If you'd rather not run both, pick one and disable the other. > **My plan isn't Enterprise — can I still use Logpush?** > > No. Logpush is Enterprise-only on Cloudflare. Use the **[Cloudflare Worker](/setup/cloudflare-worker)** path instead — it works on Free, Pro, Business, and Enterprise. ## Removing the integration 1. Cloudflare → **Analytics & Logs → Logpush** → disable or delete the job 2. Searchable → **LLM Analytics → Setup → Tokens** → revoke the token Both sides are independent — revoking the token alone is enough to stop ingestion immediately, even if the job stays configured (its deliveries will start returning `401`). ## Next steps > **[Cloudflare Worker](/setup/cloudflare-worker)** > > Edge Worker option — works on every Cloudflare plan. > **[See the data](/using-searchable/visibility-tracking)** > > Open LLM Analytics to see which assistants are crawling your site. # Send Cloudflare traffic to Searchable (Worker) Source: https://docs.searchable.com/setup/cloudflare-worker Deploy a small Cloudflare Worker that forwards AI-bot requests to Searchable. Works on every Cloudflare plan, including Free. ## What this does The Worker sits at Cloudflare's edge in front of your origin. For every inbound request it: 1. Lets the request flow through to your origin unchanged (no added latency for users) 2. Checks the user agent against Searchable's AI-bot registry 3. If it matches, fires a fire-and-forget POST to Searchable with the request metadata The bot registry is fetched from Searchable and refreshed hourly, so new AI crawlers are picked up automatically — you never need to redeploy. > [!NOTE] > > **Works on any Cloudflare plan, including Free.** Most sites stay well within the Workers free tier (100k requests/day). High-traffic sites can upgrade to Workers Paid for a few dollars a month. ## Prerequisites > [!TIP] > > A Cloudflare account with your domain on it (the domain doesn't have to be on a paid plan) > [!TIP] > > Permission to deploy Workers and add Worker routes to your zone > [!TIP] > > A Searchable project with your domain confirmed ## Setup **1. Generate the Worker script in Searchable** 1. Open your Searchable dashboard 2. Go to **LLM Analytics → Setup** 3. Pick **Cloudflare Worker** as your crawler source 4. Click **Generate token** Searchable templates the token directly into the Worker script in the next panel — no find-and-replace needed. Copy the whole script. > [!TIP] > > The Searchable UI is the easiest way to get the script — the token is auto-filled and you avoid typos. The reference script is below if you'd rather build it yourself. **2. Create a new Worker in Cloudflare** Open [dash.cloudflare.com](https://dash.cloudflare.com/) → **Workers & Pages** → **Create**. 1. Pick the **Hello World** template 2. Name it (e.g. `searchable-tracker`) 3. Click **Deploy** **3. Paste the Worker script** From the new Worker's overview page: 1. Click **Edit code** 2. Replace the entire file contents with the script you copied from Searchable 3. Click **Save and deploy** **4. Bind the Worker to your domain** A Worker by itself isn't on your domain — it has its own `*.workers.dev` URL. To make it observe your real traffic, add a route. From the Worker's **Settings** tab: 1. Open **Domains & Routes** (older UIs label this **Triggers**) 2. Click **Add → Route** 3. Enter your route, e.g. `*yourdomain.com/*` (the leading `*` matches all subdomains) 4. Pick your zone and save **5. Verify in Searchable** Return to **LLM Analytics → Setup** in your Searchable dashboard. The status strip should show **Connected** within a few minutes once an AI bot hits your site. ## Reference: Worker script If you'd rather not copy the script from the Searchable UI, here's the reference. Replace `INTEGRATION_TOKEN` with the token you generated. ```js worker.js theme={null} const SEARCHABLE_ENDPOINT = "https://tracker.searchableanalytics.com/v1/cloudflare-logs"; const SEARCHABLE_BOTS_URL = "https://tracker.searchableanalytics.com/v1/bots.json"; // Quick-start: paste your token here. // Production: leave blank and set a Worker secret named INTEGRATION_TOKEN // (Settings → Variables and Secrets). The secret wins if both are set. const INTEGRATION_TOKEN = "sa_PASTE_YOUR_TOKEN_HERE"; let cachedPatterns = null; let cachedAt = 0; let loadPromise = null; const PATTERNS_TTL_MS = 60 * 60 * 1000; function ensurePatternsLoaded() { if (loadPromise) return; loadPromise = (async () => { try { const resp = await fetch(SEARCHABLE_BOTS_URL, { cf: { cacheTtl: 3600 } }); if (!resp.ok) return; const artifact = await resp.json(); const patterns = (artifact.bots || []) .filter((b) => b.user_agent_pattern) .map((b) => { try { return new RegExp(b.user_agent_pattern, "i"); } catch { return null; } }) .filter(Boolean); if (patterns.length > 0) { cachedPatterns = patterns; cachedAt = Date.now(); } } catch {} loadPromise = null; })(); } const STATIC_ASSET_RE = /\.(css|js|mjs|map|woff2?|ttf|otf|eot|svg|png|jpe?g|gif|webp|avif|ico|mp3|mp4|webm|pdf)(\?|$)/i; function shouldForward(ua) { if (!cachedPatterns) return true; if (!ua) return true; return cachedPatterns.some((re) => re.test(ua)); } async function forwardToSearchable(request, ua, token) { try { const url = new URL(request.url); const cf = request.cf ?? {}; const entry = { RayID: request.headers.get("cf-ray") ?? crypto.randomUUID(), ClientRequestMethod: request.method, ClientRequestURI: url.pathname, ClientRequestPath: url.pathname, ClientRequestHost: url.hostname, ClientRequestScheme: url.protocol.replace(":", ""), ClientRequestUserAgent: ua, ClientRequestReferer: request.headers.get("referer") ?? "", ClientIP: request.headers.get("cf-connecting-ip") ?? "0.0.0.0", ClientCountry: cf.country ?? "", EdgeResponseStatus: 0, EdgeStartTimestamp: Date.now() * 1_000_000, OriginResponseTime: 0, EdgeColoCode: cf.colo ?? "", CacheStatus: "", EdgeResponseBytes: 0, }; await fetch(SEARCHABLE_ENDPOINT, { method: "POST", headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/x-ndjson", "X-Searchable-Source": "cloudflare-worker", }, body: JSON.stringify(entry) + "\n", }); } catch {} } export default { async fetch(request, env, ctx) { ctx.passThroughOnException(); if (STATIC_ASSET_RE.test(request.url)) return fetch(request); if (!cachedPatterns || Date.now() - cachedAt > PATTERNS_TTL_MS) { ensurePatternsLoaded(); } const ua = request.headers.get("user-agent") ?? ""; const token = env.INTEGRATION_TOKEN || INTEGRATION_TOKEN; if (token && shouldForward(ua)) { ctx.waitUntil(forwardToSearchable(request, ua, token)); } return fetch(request); }, }; ``` A few things worth knowing about this script: * **It never blocks the response.** The forward to Searchable is `ctx.waitUntil(...)`, so even if Searchable is unreachable, your users see no slowdown. * **It fails open.** While the bot registry is loading (cold start) the Worker forwards every request and lets Searchable's server-side classifier do the filtering. Once the registry loads, it filters at the edge to cut traffic. * **Static assets are skipped.** CSS, JS, images, fonts, and video bypass the Worker entirely — those almost never come from AI crawlers, and there's no point burning a Worker invocation on each. ## Storing the token as a secret (recommended) For tighter security, store the token as a Worker secret instead of leaving it inline. 1. In your Worker, open **Settings → Variables and Secrets** 2. Add a secret named `INTEGRATION_TOKEN` with your `sa_…` token as the value 3. In the script, blank the inline `INTEGRATION_TOKEN` constant: `const INTEGRATION_TOKEN = "";` 4. Redeploy The Worker reads the secret if present and falls back to the constant otherwise. Same script, no other edits. This means you can rotate the token in Cloudflare's UI without ever touching the Worker code. ## Cost Each AI-bot request costs you one extra Worker invocation. The Workers Free plan includes 100,000 requests per day — that's about 3 million crawls per month, which covers nearly all sites. If you exceed it, the [Workers Paid plan](https://www.cloudflare.com/plans/developer-platform-pricing/) is \$5/month for 10 million requests. The Worker itself does no CPU-intensive work (a regex check and a `fetch` call), so CPU time is negligible. ## Troubleshooting > **Status stays on 'Waiting for first event'** > > Most likely the route isn't bound correctly. > > * In Cloudflare, open the Worker → **Settings → Domains & Routes** > * Confirm the route pattern matches your live domain (e.g. `*yourdomain.com/*`, not `*yourdomain.dev/*`) > * Confirm the route's **Zone** is the zone that's actually serving traffic for the domain > > If the route is right, hit your site with a curl using a known AI user agent: > > ```bash theme={null} > curl -H "User-Agent: GPTBot/1.0 (+https://openai.com/gptbot)" https://yourdomain.com/ > ``` > > Then click **Check** in Searchable. If that doesn't appear in the dashboard, the Worker's logs (Cloudflare → Worker → **Logs**) will show whether `forwardToSearchable` is running and whether Searchable returned a non-2xx status. > **Worker logs show 401 from Searchable** > > The token is missing or wrong. > > * Confirm `INTEGRATION_TOKEN` (inline) or the secret named `INTEGRATION_TOKEN` is set to a `sa_…` value > * If you've recently revoked the token in Searchable, generate a new one and update the Worker > * The token must have **no quotes** around it in the secret value > **My site is fronted by Cloudflare but I don't manage the zone** > > You need permission to add Worker routes on the zone. If you don't have it: > > * Ask the zone admin to add the route, or > * Use the **[custom REST API](/setup/custom)** path from your application server > **I see double-counted events** > > If you also have **[Cloudflare Logpush](/setup/cloudflare)** sending traffic to Searchable, you'll get the same request from both sources. Searchable de-duplicates server-side using the Cloudflare Ray ID, so the dashboard shows each request once. If you'd rather not run both, pick one and disable the other. ## Removing the integration 1. Cloudflare → Worker → **Settings → Domains & Routes** → remove the route (the Worker stops observing traffic) 2. Optionally delete the Worker itself 3. Searchable → **LLM Analytics → Setup → Tokens** → revoke the token ## Next steps > **[Cloudflare Logpush](/setup/cloudflare)** > > On the Enterprise plan? See the native Logpush path. > **[See the data](/using-searchable/visibility-tracking)** > > Open LLM Analytics to see which assistants are crawling your site. # Send Amazon CloudFront traffic to Searchable Source: https://docs.searchable.com/setup/cloudfront Stream CloudFront standard logs to Searchable through Amazon Data Firehose — works on any CloudFront distribution ## What this does CloudFront's [Standard Logging V2](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/standard-logging.html) ships request logs to a destination of your choosing. We point a Kinesis Data Firehose stream at Searchable's ingest endpoint, Firehose batches log records to us, we classify the AI bots, and drop everything else. > [!NOTE] > > **No code changes.** All configuration is done inside the AWS Console. ## Prerequisites > [!TIP] > > A CloudFront distribution (works on any plan — Standard Logging V2 is a free CloudFront feature) > [!TIP] > > Permission to create Firehose delivery streams and edit the CloudFront distribution > [!TIP] > > A Searchable project with your domain confirmed > [!TIP] > > You only pay for the Firehose ingestion + delivery (typically a few cents per GB of AI-bot traffic). There is no CloudFront charge for enabling Standard Logging V2. ## Setup **1. Generate an integration token in Searchable** 1. Open your Searchable dashboard 2. Go to **LLM Analytics → Setup** 3. Pick **Amazon CloudFront** as your crawler source 4. Click **Generate token** Copy the token now — it starts with `sa_…` and won't be shown again. You can always generate a new one if you lose it. The endpoint URL is fixed: ``` https://tracker.searchableanalytics.com/v1/cloudfront-logs ``` > [!TIP] > > Sanity check before pointing Firehose at the endpoint: `curl https://tracker.searchableanalytics.com/v1/cloudfront-logs` should return `200 OK`. If you don't see that, your network can't reach the endpoint and Firehose will silently buffer to the S3 backup bucket instead. **2. Open the CloudFront distribution's Logging tab** Open the [CloudFront console](https://console.aws.amazon.com/cloudfront/v4/home) → your distribution → **Logging**. Click **Add standard log destination**. | Field | Value | | - | - | | **Destination type** | Amazon Data Firehose | | **Output format** | JSON | Then click **Create new Firehose stream** — this opens the Firehose wizard in a new tab. **3. Create the Firehose delivery stream** In the Firehose wizard: | Field | Value | | - | - | | **Source** | Direct PUT | | **Destination** | HTTP Endpoint | | **HTTP endpoint URL** | `https://tracker.searchableanalytics.com/v1/cloudfront-logs` | | **Access key** | paste the `sa_…` token from Searchable | | **Content encoding** | GZIP | | **Buffer hints** | **1 MiB** / **60s** | > [!WARNING] > > Buffer hints matter. The Firehose default is 5 MiB, which sits right at Searchable's 5 MB compressed-batch cap — high-traffic distributions can intermittently hit `413 Payload Too Large`. Set the size hint to **1 MiB** and the interval to **60 seconds**. > [!TIP] > > Firehose can't set arbitrary request headers, so it sends the **Access key** value as the `X-Amz-Firehose-Access-Key` header instead. Searchable's endpoint accepts either that or `Authorization: Bearer sa_…`, so the same token works for Firehose, a custom Lambda relay, or a curl test. **4. Configure the S3 backup bucket** Firehose requires an S3 backup bucket for records it can't deliver. | Field | Value | | - | - | | **S3 backup bucket** | any bucket you control | | **Backup mode** | **Failed data only** | "Failed data only" means you only pay to store records the endpoint actually rejects (for example, after a token revocation). Don't pick "All data" — it would duplicate every CloudFront log into S3 unnecessarily. Finish creating the Firehose stream. **5. Finish the CloudFront log destination** Return to the CloudFront tab and select the Firehose stream you just created. Pick these standard log fields: **Required** (the endpoint drops records missing any of these): * `timestamp` * `sc-status` * `cs-method` * `cs-uri-stem` * `x-host-header` * `cs-user-agent` **Recommended** (improves enrichment + debugging; anything you select beyond the required set is preserved as `custom_properties` in Searchable): * `c-ip` * `c-country` * `cs-protocol` * `cs-uri-query` * `cs-referer` * `sc-bytes` * `time-taken` * `x-edge-request-id` * `x-edge-location` * `x-edge-result-type` Save the log destination. **6. Verify in Searchable** Firehose batches every \~60 seconds, so events take a moment to appear after the first AI bot hit. Return to **LLM Analytics → Setup** in Searchable. The Amazon CloudFront card should show **Connected** within a few minutes once an AI bot hits your site. ## Verifying the connection In Searchable: 1. Go to **LLM Analytics → Setup** 2. Look at the Amazon CloudFront card status 3. Click **Check** if it still shows "Waiting for first event" | Status | What it means | | - | - | | **Waiting for first event** | The Firehose stream is configured but no AI bot has hit your site yet. Typical wait is a few hours for sites that are already indexed. | | **Connected** | Events are arriving. The card shows the count from the last 24 hours. | You can also confirm in AWS: **Amazon Data Firehose → your stream → Monitoring**. The stream's metrics show incoming records (from CloudFront) and successful HTTP deliveries (to Searchable). The S3 backup bucket should stay near-empty when "Failed data only" is set. ## What Searchable receives For each request that matches an AI-bot user agent, Searchable receives: * HTTP method, path, and host (query strings stripped before storage) * User agent * Referer * Country code (from `c-country`) * Response status, response bytes * Edge timing (`time-taken`) * CloudFront edge metadata (`x-edge-request-id`, `x-edge-location`, `x-edge-result-type`) — preserved as `custom_properties` for debugging Bodies, headers other than `User-Agent` / `Referer`, cookies, and full IPs are never sent or stored. The CloudFront edge request ID is also used to de-duplicate Firehose redeliveries server-side. ## Multiple distributions Each Firehose stream is bound to a single CloudFront distribution's log destination. If your domain spans multiple distributions (for example, separate distributions for `example.com` and `assets.example.com`): 1. Generate one integration token, or reuse one across distributions — both work 2. Create one Firehose stream per distribution, all pointing at the same HTTP endpoint 3. Searchable tags events by host, so you'll still see them split by domain in the dashboard ## Troubleshooting > **Firehose 'Destination error count' is climbing** > > Open the Firehose stream in the AWS Console → **Monitoring → Destination error logs** for the specific error. The common ones: > > * **`401 Unauthorized`** — the `sa_…` token in the **Access key** field is missing, wrong, or has been revoked in Searchable. Re-paste the token, or generate a new one from **LLM Analytics → Setup**. > * **`413 Payload Too Large`** — the Firehose buffer hint is too big. Drop the size hint to **1 MiB** and the interval to **60 seconds**, then save. Firehose will retry the buffered batches once they shrink. > * **`400 Bad Request`** — the CloudFront log output format isn't JSON, or the required fields aren't selected. Edit the CloudFront log destination, set **Output format** to **JSON**, and confirm `timestamp`, `sc-status`, `cs-method`, `cs-uri-stem`, `x-host-header`, and `cs-user-agent` are all checked. > > While errors are climbing, Firehose buffers the affected records into your S3 backup bucket — fix the root cause and the stream catches back up automatically. > **Status stays on 'Waiting for first event'** > > CloudFront only delivers logs for traffic that's actually being served by the distribution. Things to check: > > * The CloudFront distribution is **Enabled** (not disabled or paused) > * The Firehose stream is **Active** (not in `CREATING` or `DELETING` state) > * Your domain in Searchable matches the alternate domain (CNAME) on the distribution (check **LLM Analytics → Setup → Confirm your domain**) > * The log destination is attached to the distribution — easy to miss if the Firehose-creation wizard was opened separately and you forgot to come back to CloudFront's Logging tab > > If everything looks right, hit your site with a known AI user agent and wait \~60 seconds for the next Firehose batch: > > ```bash theme={null} > curl -H "User-Agent: GPTBot/1.0 (+https://openai.com/gptbot)" https://yourdomain.com/ > ``` > **Records keep landing in the S3 backup bucket** > > The "Failed data only" backup bucket only fills up when the HTTP endpoint rejects records — typically a token / config issue. Inspect a recent failed object in the bucket: > > * It contains the original CloudFront records along with an error message from the endpoint (`401`, `413`, `400`, etc.) > * Use the message to identify the root cause (see the `Firehose 'Destination error count' is climbing` section above) > * Once you fix the config, new records flow through to Searchable; the records already in the backup bucket aren't automatically re-delivered (Firehose treats the backup as terminal storage) > **I see duplicated events** > > Firehose is at-least-once, so it can occasionally redeliver a batch after a transient network error. Searchable de-duplicates on `x-edge-request-id` server-side, so duplicate events don't appear in the dashboard — provided you selected `x-edge-request-id` in the CloudFront log fields. If you skipped it, dedup falls back to a (timestamp, path, user-agent) heuristic that's less precise. Edit the log destination, add `x-edge-request-id`, and save. > **I'd rather pipe logs through a custom Lambda instead of Firehose** > > The same endpoint accepts plain NDJSON POSTs (no Firehose envelope), so you can post log records directly from a Lambda or any custom collector. Use: > > * `POST https://tracker.searchableanalytics.com/v1/cloudfront-logs` > * `Authorization: Bearer sa_…` header > * `Content-Type: application/x-ndjson` (gzip optional — set `Content-Encoding: gzip` if you compress) > * One CloudFront log record per line, using the same field names as Standard Logging V2 (`cs-method`, `cs-uri-stem`, `x-host-header`, `cs-user-agent`, `sc-status`, `timestamp`, etc.) > > The endpoint replies with `204 No Content` on success. ## Removing the integration 1. AWS Console → **CloudFront → your distribution → Logging** → delete the standard log destination 2. AWS Console → **Amazon Data Firehose → your stream** → delete the stream (and the S3 backup bucket if you no longer need it) 3. Searchable → **LLM Analytics → Setup → Tokens** → revoke the token Both sides are independent — revoking the token alone is enough to stop ingestion immediately, even if the Firehose stream stays configured (its deliveries will start returning `401` and land in the backup bucket). ## Next steps > **[See the data](/using-searchable/visibility-tracking)** > > Open LLM Analytics to see which assistants are crawling your site. > **[Add Search Console](/integrations/google-search-console)** > > Correlate AI crawls with search demand. # Custom integration Source: https://docs.searchable.com/setup/custom Ship AI-bot traffic to Searchable from any stack — Next.js, Express, Fastify, Go, Python, Ruby, anything that can make an HTTP call — using middleware or the REST API. ## What this is When your hosting platform doesn't have a first-class connector (or you just want full control), Searchable accepts events directly from your application via two paths: > **[Middleware SDK](/setup/middleware)** > > Drop-in Node.js package — `@searchablehq/middleware`. Wraps Next.js middleware with one line. > The primitives also compose into Express, Fastify, or anything else on Node. > **[REST API](/setup/rest-api)** > > Direct HTTP POST to Searchable's ingest endpoint. Use from any language — Go, Python, Ruby, PHP, > a shell script, or a CDN worker we don't yet support natively. Both paths land in the same place. They share the same auth model (workspace API key + project site token) and feed the same dashboards. > [!NOTE] > > **No data is mocked.** Events you POST flow through the same pipeline as our native Vercel, > Cloudflare, and Netlify connectors, and the same server-side bot classifier filters non-AI user > agents. ## Which one should I use? > **I run a Next.js app** > > Use the **[middleware SDK](/setup/middleware)**. One import, one config object, done. It plugs into `middleware.ts` and captures every non-static request automatically. > **I run Express or Fastify on Node** > > Use the **[middleware SDK's > primitives](/setup/middleware#express-fastify-other-node-frameworks)**. The package exports > `buildEventPayload` and `sendEvent` so you can wire it into any Node framework — typically as a > request-completion hook or `res.on("finish")` listener. > **I'm on a stack the SDK doesn't cover (Go, Python, Ruby, PHP, …)** > > Use the **[REST API](/setup/rest-api)**. It's a single `POST /v1/events` call with `Authorization: > Bearer sk_live_…` and a JSON body — language-agnostic, no SDK needed. > **I want to test the wiring before instrumenting my app** > > Start with a `curl` against the **[REST API](/setup/rest-api)** using a fake AI user agent (e.g. > `GPTBot/1.0`). The status strip in **LLM Analytics → Setup** flips to **Connected** within a few > seconds. Once that's green, switch to your real integration. > **I have an in-house CDN worker (e.g. on a platform we don't list)** > > Use the **[REST API](/setup/rest-api)** from your worker. Cloudflare's native `/v1/cloudflare-logs` endpoint is the same shape — if you're forwarding NDJSON request logs from a Cloudflare-like worker, point them at `/v1/cloudflare-logs` instead and reuse the [Cloudflare Worker setup](/setup/cloudflare-worker). ## Common prerequisites Both paths need the same two credentials. **1. Site token (per project)** 1. Open your Searchable dashboard 2. Go to **LLM Analytics → Setup → Confirm your domain** 3. The private **Site token** for this project starts with `st_…` — copy it The site token identifies which project events belong to. It's tied to the project's primary domain. > [!TIP] > > Searchable also issues a **public** site token (`pst_…`) for the browser beacon. That one is *not* the right one for server-side integrations — use the private `st_…` token here. **2. Workspace API key** 1. In the same Setup page, click **Custom** as your crawler source 2. Open the connector dialog → **Generate API key** 3. Searchable creates a key with the `log_events` permission, scoped to the current project, and shows it once — copy it now Keys start with `sk_live_…`. They're signed JWT-style tokens — the worker verifies them at Cloudflare's edge with no DB round-trip, so they don't add per-request latency. > [!WARNING] > > The API key is only shown once. If you lose it, revoke and regenerate from **Settings → API Keys** or the Custom connector dialog. ## What ends up in Searchable For each event you send, Searchable records: * HTTP method, path, host (query strings are stripped before storage) * Status code, response time, response bytes * User agent (used to classify the AI bot) * Referer and referrer domain * UTM parameters * Geo country (from the inbound IP, if available) * Timestamp Cookies, request/response bodies, and full IP addresses are never stored. The middleware SDK anonymizes IPs by default (zeroes the last octet) before sending. The server-side classifier drops any user agent that isn't a known AI crawler — so even if you POST every request from your app, the dashboard only shows GPTBot, ClaudeBot, PerplexityBot, and the rest of the AI-bot universe. ## How auth flows ``` Your app ──POST──→ tracker.searchableanalytics.com │ │ Authorization: Bearer sk_live_… │ Body: { site_token: "st_…", events: [...] } ▼ Verify HMAC signature on sk_live_… at the edge (no DB lookup — fast fail on 401 / 403) │ ▼ Resolve site_token → workspace + domain │ ▼ Forward to ingest → ClickHouse ``` `sk_live_*` carries `{ workspace_id, key_id }` in its signed payload. The worker verifies the HMAC at Cloudflare's edge and tags every forwarded event with both values, so revoking a key in the dashboard immediately stops any in-flight POSTs that use it. ## Pick a path > **[Middleware SDK setup](/setup/middleware)** > > Step-by-step for Next.js + a recipe for Express/Fastify. > **[REST API reference](/setup/rest-api)** > > Endpoint, headers, payload schema, and a `curl` you can run today. ## Next steps > **[See the data](/using-searchable/visibility-tracking)** > > Open LLM Analytics to see which assistants are crawling your site. > **[Add Search Console](/integrations/google-search-console)** > > Layer in keyword data so you can correlate AI crawls with search demand. # Send Fastly traffic to Searchable (Real-Time Log Streaming) Source: https://docs.searchable.com/setup/fastly-logs Configure a Fastly HTTPS Logging endpoint to stream request logs to Searchable. No code changes — all configuration is done inside the Fastly UI. ## What this does Fastly's Real-Time Log Streaming ships request log records to a destination of your choosing in near-real-time. We point that stream at Searchable's tracker endpoint, classify the AI bots, and drop everything else. No code changes — all configuration happens inside your Fastly service. > [!NOTE] > > **No code changes.** All configuration is done inside the Fastly UI. ## Prerequisites > [!TIP] > > A Fastly account on any paid plan (HTTPS Logging is available on all paid plans) > [!TIP] > > Admin or Engineer-level access to the Fastly service you want to instrument > [!TIP] > > A Searchable project with your domain confirmed ## Setup **1. Generate an integration token in Searchable** 1. Open your Searchable dashboard 2. Go to **LLM Analytics → Setup** 3. Pick **Fastly** as your crawler source 4. Click **Generate token** Copy the token now — it starts with `sa_…` and won't be shown again. You can always generate a new one if you lose it. **2. Create an HTTPS logging endpoint in Fastly** In the Fastly UI, navigate to your service: **Service → Logging → HTTPS → Create endpoint** This creates a new draft version of your service config; you'll activate it at the end. **3. Configure the endpoint** Fill in these fields: | Field | Value | | - | - | | **Name** | `Searchable LLM Analytics` (or any label you prefer) | | **URL** | `https://tracker.searchableanalytics.com/v1/fastly-logs` | | **Method** | `POST` | | **Content type** | `application/json` | | **Log format version** | `2` | | **Custom header name** | `Authorization` | | **Custom header value** | `Bearer ` | | **Log format** | (paste the template below) | > [!TIP] > > The custom header value must be `Bearer ` followed by the full `sa_…` token, with one space and no quotes. The header name must be exactly `Authorization`. **4. Paste the log format template verbatim** Paste this into the **Log format** field exactly as-is. Do **not** edit any of the `%{...}` substitutions — they are Fastly VCL/log-format directives that we depend on for correct request attribution and deduplication: ```json theme={null} { "timestamp": "%{begin:%Y-%m-%dT%H:%M:%SZ}t", "client_ip": "%{req.http.Fastly-Client-IP}V", "method": "%{req.method}V", "url": "%{req.url}V", "host": "%{req.http.host}V", "status": %{resp.status}V, "response_time": %{time.elapsed.msec}V, "user_agent": "%{json.escape(req.http.User-Agent)}V", "referer": "%{json.escape(req.http.Referer)}V", "fastly_request_id": "%{req.xid}V", "country": "%{client.geo.country_code}V", "region": "%{client.geo.region}V", "city": "%{json.escape(client.geo.city)}V" } ``` > [!WARNING] > > `fastly_request_id` must use `%{req.xid}V` — Fastly's canonical per-request unique identifier. Substituting any other token (e.g. `Fastly-SOA-ID` or a custom `X-Request-ID`) will silently break deduplication on our side and events will appear to be lost. **5. Save and activate the new service version** Click **Create** to save the endpoint, then **Activate** the new service version. Fastly batches log records and ships them within a few seconds; expect events to appear in Searchable within a couple of minutes once an AI bot hits your site. ## Verifying the connection In Searchable: 1. Go to **LLM Analytics → Setup** 2. Look at the Fastly card status 3. Click **Check** if it still shows "Waiting for first event" | Status | What it means | | - | - | | **Waiting for first event** | The endpoint is configured but no AI bot has hit your site yet. Typical wait is a few hours for sites that are already indexed. | | **Connected** | Events are arriving. The card shows the count from the last 24 hours. | ## Geo enrichment `country`, `region`, and `city` arrive from Fastly's edge geo lookups (`client.geo.*`). If geo lookups aren't enabled on your service (the default is on for paid plans), those fields will arrive empty — everything else still works, and the integration counts as healthy. You can enable geo at **Service → Settings → Geolocation**. ## Troubleshooting > **Searchable shows 401 errors or the card stays 'Not connected'** > > The Authorization header is missing or wrong. > > * Make sure you added a **custom header** named exactly `Authorization` > * The value must be `Bearer ` followed by the full `sa_…` token, with one space and no quotes > * If you've recently revoked the token in Searchable, generate a new one and update the endpoint > **Fastly reports delivery errors or 400 responses** > > The log format is likely wrong. > > * Confirm **Log format version** is set to **2**, not 1 — version 1 wraps each record in a different shape that our endpoint doesn't accept > * Confirm the log format template was pasted exactly, including all `%{...}` substitutions. Do not remove any directive, even if it looks redundant > * Confirm the **Content type** is `application/json` > **Events look deduplicated incorrectly (e.g. many requests collapse to one row)** > > `fastly_request_id` is being populated with a non-unique value. > > * Confirm the template uses `%{req.xid}V` (not `%{req.http.Fastly-SOA-ID}V` or any other header) > * `req.xid` is auto-populated by Fastly on every request and requires no special VCL setup — if it's empty in your logs, contact Fastly support > **Status stays on 'Waiting for first event' for more than 24 hours** > > A few possible causes: > > * The new service version was never activated — go to **Service → Versions** and confirm the version containing the HTTPS endpoint is currently **Active** > * Your domain in Searchable doesn't match the site served by Fastly (check **LLM Analytics → Setup → Confirm your domain**) > * The endpoint is configured but no AI bot has visited yet — try visiting your site with a known AI user agent (e.g. `Mozilla/5.0 (compatible; GPTBot/1.0)`) to trigger a test event > > If deliveries are succeeding in Fastly's logs view but nothing appears in Searchable, that points to a domain mismatch or an Authorization-header issue. ## Removing the integration To stop sending traffic to Searchable: 1. Fastly → **Service → Logging → HTTPS** → delete the endpoint and activate a new service version 2. Searchable → **LLM Analytics → Setup → Tokens** → revoke the token Both sides are independent — revoking the token alone is enough to stop ingestion immediately, even if the endpoint stays configured in Fastly (its deliveries will start returning `401`). ## Next steps > **[See the data](/using-searchable/visibility-tracking)** > > Open LLM Analytics to see which assistants are crawling your site. > **[Add Search Console](/integrations/google-search-console)** > > Correlate AI crawls with search demand. # Send Google Cloud traffic to Searchable Source: https://docs.searchable.com/setup/gcp-logs Forward GCP HTTPS Load Balancer and Cloud CDN logs to Searchable via a Pub/Sub push subscription routed through a small Cloud Run relay. ## What this does Google Cloud Logging captures every request that hits your HTTPS Load Balancer or Cloud CDN. You'll route those logs to a Pub/Sub topic, push them through a small Cloud Run relay inside your project, and forward them to Searchable. We classify AI bots at our edge and drop everything else. > [!NOTE] > > Everything runs inside your GCP project. The relay is stateless, idempotent, and only forwards > payloads — it doesn't read or store them. ## Prerequisites > [!TIP] > > A GCP project running an HTTPS Load Balancer or Cloud CDN > [!TIP] > > `roles/pubsub.admin`, `roles/logging.admin`, and `roles/run.admin` on that project (or equivalent) > [!TIP] > > `gcloud` CLI installed and authenticated, or access to Cloud Shell > [!TIP] > > A Searchable project with your domain confirmed ## Setup **1. Generate an integration token in Searchable** 1. Open your Searchable dashboard 2. Go to **LLM Analytics → Setup** 3. Pick **Google Cloud Platform** as your crawler source 4. Click **Generate token** Copy the token now — it starts with `sa_…` and won't be shown again. You can always generate a new one if you lose it. **2. Create the Pub/Sub topic** In Cloud Shell (or any terminal with `gcloud` authenticated to the target project): ```bash theme={null} gcloud pubsub topics create searchable-ai-traffic ``` This is a dedicated topic that will hold log entries en route to Searchable. Keeping it separate from any other logging pipeline you have makes troubleshooting and removal trivial. **3. Deploy the Cloud Run relay** Create an empty directory and add two files. The relay is a single HTTP handler that adds the `X-Searchable-Token` header and forwards the Pub/Sub push body to Searchable unchanged. `package.json`: ```json theme={null} { "name": "searchable-relay", "type": "module", "main": "index.js", "scripts": { "start": "node index.js" }, "dependencies": { "hono": "^4.0.0", "@hono/node-server": "^1.0.0" } } ``` `index.js`: ```js theme={null} import { Hono } from "hono"; import { serve } from "@hono/node-server"; const app = new Hono(); app.get("/", (c) => c.text("ok", 200)); app.post("/", async (c) => { const body = await c.req.text(); const upstream = await fetch(process.env.SEARCHABLE_ENDPOINT, { method: "POST", headers: { "content-type": "application/json", "X-Searchable-Token": process.env.SEARCHABLE_TOKEN, }, body, }); return c.body(null, upstream.status); }); serve({ fetch: app.fetch, port: parseInt(process.env.PORT ?? "8080", 10) }); ``` Create a dedicated service account for Pub/Sub to invoke the relay as, deploy with `--no-allow-unauthenticated`, then grant it `roles/run.invoker`. Replace `` with the token from step 1: ```bash theme={null} gcloud iam service-accounts create searchable-pubsub-invoker \ --display-name="Searchable Pub/Sub invoker" gcloud run deploy searchable-relay \ --source=. \ --region=us-central1 \ --no-allow-unauthenticated \ --set-env-vars=SEARCHABLE_ENDPOINT=https://tracker.searchableanalytics.com/v1/gcp-logs,SEARCHABLE_TOKEN= gcloud run services add-iam-policy-binding searchable-relay \ --region=us-central1 \ --member=serviceAccount:searchable-pubsub-invoker@$(gcloud config get-value project).iam.gserviceaccount.com \ --role=roles/run.invoker ``` Cloud Run will reject any invocation that isn't signed by `searchable-pubsub-invoker`, so the relay can't be called by anyone who happens to find the URL. > [!TIP] > > For production, store the token in Secret Manager instead of passing it via `--set-env-vars` (which leaves the cleartext value in `gcloud run services describe` output and your shell history). Create a secret, grant the Cloud Run runtime service account `roles/secretmanager.secretAccessor`, then swap `--set-env-vars=SEARCHABLE_TOKEN=...` for `--set-secrets=SEARCHABLE_TOKEN=projects//secrets/:latest`. Copy the **Service URL** printed by `gcloud run deploy` — you'll use it in the next step. **4. Create the push subscription** Replace `` with the Service URL from the previous step. The subscription mints an OIDC token from `searchable-pubsub-invoker` for every push: ```bash theme={null} gcloud pubsub subscriptions create searchable-ai-traffic-sub \ --topic=searchable-ai-traffic \ --push-endpoint= \ --push-auth-service-account=searchable-pubsub-invoker@$(gcloud config get-value project).iam.gserviceaccount.com \ --ack-deadline=30 \ --min-retry-delay=10s \ --max-retry-delay=600s ``` The Searchable integration token is injected by the relay; the OIDC service-account auth here is what gates access to the relay itself. **5. Create the Log Sink** Route HTTPS Load Balancer and Cloud CDN logs into the topic: ```bash theme={null} gcloud logging sinks create searchable-ai-traffic-sink \ pubsub.googleapis.com/projects/$(gcloud config get-value project)/topics/searchable-ai-traffic \ --log-filter='resource.type="http_load_balancer" OR resource.type="cloud_cdn"' ``` GCP prints a service-account email after this command — grant it `roles/pubsub.publisher` on your project. The exact `gcloud` command is shown in the output; alternatively in the Console you'll see a yellow banner asking you to authorize the sink. ## Verifying the connection In Searchable: 1. Go to **LLM Analytics → Setup** 2. Look at the Google Cloud Platform card status 3. Click **Check** if it still shows "Waiting for first event" | Status | What it means | | - | - | | **Waiting for first event** | The subscription is configured but no AI bot has hit your site yet. Typical wait is a few hours for sites that are already indexed. | | **Connected** | Events are arriving. The card shows the count from the last 24 hours. | ## Geo enrichment caveat GCP HTTPS Load Balancer logs do **not** include geographic enrichment by default — `country`, `region`, and `city` will arrive empty in Searchable. The integration is otherwise fully functional. If you need geo, the simplest workaround is to also instrument your site with the Searchable Beacon (`s.js`), which derives geo from the visitor's request. ## Troubleshooting > **Searchable shows 401 errors or the card stays 'Not connected'** > > The relay isn't sending the `X-Searchable-Token` header — either it's misconfigured or the token has been revoked. > > * Confirm the Cloud Run service has `SEARCHABLE_TOKEN` set. List env var **names** only (not values) with `gcloud run services describe searchable-relay --region=us-central1 --format='value(spec.template.spec.containers[0].env[].name)'`. If you used Secret Manager, the entry shows as `SEARCHABLE_TOKEN` and the value lives in the secret > * If you've recently revoked the token in Searchable, generate a new one and redeploy the relay with the new value > **Pub/Sub reports delivery errors or messages pile up unacked** > > Pub/Sub can't reach the relay, or the log filter is sending non-HTTP logs. > > * Verify the subscription's push endpoint in the Console (Pub/Sub → Subscriptions → searchable-ai-traffic-sub → Edit) matches the deployed Cloud Run URL > * Check Cloud Run logs (`gcloud run services logs read searchable-relay --region=us-central1 --limit=50`) for non-200 responses — those become Pub/Sub retries > * Verify the sink filter is exactly `resource.type="http_load_balancer" OR resource.type="cloud_cdn"`. Broader filters can send non-HTTP logs that we reject with 204 (no retry) but also waste your Pub/Sub quota > **The sink isn't routing logs to the topic** > > GCP requires the sink's service account to have `roles/pubsub.publisher` on the topic. Without it the sink silently drops messages. > > * Run `gcloud logging sinks describe searchable-ai-traffic-sink` to find the writer service account > * Grant it publisher on the topic: `gcloud pubsub topics add-iam-policy-binding searchable-ai-traffic --member=serviceAccount: --role=roles/pubsub.publisher` > **`gcloud run deploy` fails with an org-policy or ingress error** > > Some GCP orgs enforce policies that further restrict Cloud Run. The default setup already deploys with `--no-allow-unauthenticated`, which satisfies most org policies — these errors usually point at one of the others: > > * `constraints/run.allowedIngress` forces internal-only: redeploy with `--ingress=internal-and-cloud-load-balancing` and place a Pub/Sub-VPC connector in front, or run the relay on Cloud Functions instead > * `constraints/iam.disableServiceAccountCreation` blocks the `gcloud iam service-accounts create` command: use an existing service account your org already trusts and reuse its email in both `add-iam-policy-binding` and `--push-auth-service-account` > * `constraints/iam.allowedPolicyMemberDomains` blocks the invoker binding: have an admin add the project's service-agent domain to the allow-list, then re-run the binding command > **Status stays on 'Waiting for first event' for more than 24 hours** > > A few possible causes: > > * The Log Sink filter doesn't match your service — confirm your HTTPS Load Balancer logs actually populate Cloud Logging (View → Logs Explorer → filter on `resource.type="http_load_balancer"` and confirm you see entries) > * Your domain in Searchable doesn't match the site served by GCP (check **LLM Analytics → Setup → Confirm your domain**) > * No AI bot has visited yet — try visiting your site with a known AI user agent (e.g. `Mozilla/5.0 (compatible; GPTBot/1.0)`) to trigger a test event > > If Pub/Sub's "Push attempt" metrics show successful 204 responses but Searchable still says no events, that points to a domain mismatch. ## Removing the integration To stop sending traffic to Searchable and tear down the GCP-side resources: ```bash theme={null} gcloud logging sinks delete searchable-ai-traffic-sink gcloud pubsub subscriptions delete searchable-ai-traffic-sub gcloud pubsub topics delete searchable-ai-traffic gcloud run services delete searchable-relay --region=us-central1 gcloud iam service-accounts delete \ searchable-pubsub-invoker@$(gcloud config get-value project).iam.gserviceaccount.com ``` Then go to Searchable → **LLM Analytics → Setup → Tokens** and revoke the integration token. Both sides are independent — revoking the token alone is enough to stop ingestion immediately, even if the GCP-side resources stay configured (push deliveries will start returning `401`, which Pub/Sub will retry until the messages expire). ## Next steps > **[See the data](/using-searchable/visibility-tracking)** > > Open LLM Analytics to see which assistants are crawling your site. > **[Add Search Console](/integrations/google-search-console)** > > Correlate AI crawls with search demand. # Send traffic to Searchable with the Middleware SDK Source: https://docs.searchable.com/setup/middleware Use @searchablehq/middleware to capture AI-bot traffic from any Node app. First-class support for Next.js, and primitives for Express, Fastify, and other Node frameworks. ## What this is `@searchablehq/middleware` is a small Node package that captures inbound requests in your application and fires a non-blocking POST to Searchable's ingest endpoint. The Searchable backend then runs the same AI-bot classifier the rest of our connectors use, so only GPTBot, ClaudeBot, PerplexityBot, and other AI agents end up in your dashboard. > [!NOTE] > > **Zero added latency.** The SDK fires events fire-and-forget after your response is built — your users never wait on Searchable. If the network is down or Searchable is unreachable, the request still completes. The package ships with a first-class `withSearchable` wrapper for Next.js middleware. The same core primitives (`buildEventPayload`, `sendEvent`) can be reused to wire any other Node framework — examples for Express and Fastify are below. ## Prerequisites > [!TIP] > > A Node 18+ application (Next.js 13+, Express, Fastify, etc.) > [!TIP] > > A Searchable project with your domain confirmed > [!TIP] > > The two credentials from the [common prerequisites](/setup/custom#common-prerequisites): a project **site token** (`st_…`) and a workspace **API key** (`sk_live_…`) ## Install ```bash theme={null} # pnpm pnpm add @searchablehq/middleware # npm npm install @searchablehq/middleware # yarn yarn add @searchablehq/middleware ``` The package has no required runtime dependencies. `next` is an optional peer dependency — only loaded when you import from `@searchablehq/middleware/nextjs`. ## Next.js setup **1. Add the credentials to your environment** In `.env.local` (or your hosting platform's env-var settings): ```bash .env.local theme={null} SEARCHABLE_SITE_TOKEN=st_your_token_here SEARCHABLE_API_KEY=sk_live_your_key_here ``` Both values come from **LLM Analytics → Setup → Custom** in your Searchable dashboard. **2. Create or update middleware.ts** In your Next.js project root: ```ts middleware.ts theme={null} import { withSearchable } from "@searchablehq/middleware/nextjs"; export default withSearchable({ siteToken: process.env.SEARCHABLE_SITE_TOKEN!, apiKey: process.env.SEARCHABLE_API_KEY!, }); export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"], }; ``` The `matcher` keeps the middleware off Next's static asset routes — those almost never come from AI crawlers, and there's no point burning a middleware invocation on each. **3. Already have a middleware.ts?** Pass your existing middleware as the second argument to `withSearchable`. Searchable runs first, then yields to your logic: ```ts middleware.ts theme={null} import { withSearchable } from "@searchablehq/middleware/nextjs"; import { NextResponse } from "next/server"; export default withSearchable( { siteToken: process.env.SEARCHABLE_SITE_TOKEN!, apiKey: process.env.SEARCHABLE_API_KEY!, }, async (request) => { // Your existing middleware logic here. return NextResponse.next(); }, ); ``` **4. Deploy** Ship the change to your hosting platform. The middleware runs on every non-static request and starts forwarding events immediately. Open **LLM Analytics → Setup** in Searchable — the status strip flips to **Connected** within a few minutes of the next AI crawler hitting your site. ## Config reference ```ts theme={null} interface SearchableConfig { /** Site token (st_*). Required. */ siteToken: string; /** Workspace API key (sk_live_*). Required — sent as `Authorization: Bearer …`. */ apiKey: string; /** Collector endpoint URL. Default: Searchable's tracker worker. */ endpoint?: string; /** Zero the last IP octet before sending. Default: true. */ anonymizeIp?: boolean; /** Log every captured event to stdout. Default: false. */ debug?: boolean; /** Skip capture for matching paths. Return true to skip. */ ignore?: (path: string) => boolean; /** Inject custom properties into the event. */ custom?: (request: Request) => Record; } ``` ### Skipping paths By default, the SDK auto-skips `/_next/*` and common static-asset extensions (`.js`, `.css`, `.png`, `.jpg`, `.svg`, `.ico`, `.woff`, `.woff2`, `.ttf`, `.map`). Add custom skips for health checks, internal APIs, or anything else you don't want to count: ```ts theme={null} withSearchable({ siteToken: process.env.SEARCHABLE_SITE_TOKEN!, apiKey: process.env.SEARCHABLE_API_KEY!, ignore: (path) => path.startsWith("/api/health") || path.startsWith("/api/internal"), }); ``` ### Custom properties Anything you return from `custom(request)` is attached to the event under `parameters` and is queryable from the dashboard: ```ts theme={null} withSearchable({ siteToken: process.env.SEARCHABLE_SITE_TOKEN!, apiKey: process.env.SEARCHABLE_API_KEY!, custom: (request) => ({ tenant: request.headers.get("x-tenant-id") ?? "unknown", abVariant: request.headers.get("x-experiment-bucket") ?? "control", }), }); ``` ### Debug mode In development, set `debug: true` to log every captured event to your terminal: ```ts theme={null} withSearchable({ siteToken: process.env.SEARCHABLE_SITE_TOKEN!, apiKey: process.env.SEARCHABLE_API_KEY!, debug: process.env.NODE_ENV === "development", }); ``` You'll see lines like: ``` [searchable/middleware] GET /pricing → 200 (45ms) ``` Turn it off in production — every event would also write to stdout. ## Express, Fastify, other Node frameworks The Next.js helper is a thin convenience wrapper around two exported primitives: ```ts theme={null} import { buildEventPayload, sendEvent, type CapturedRequest, } from "@searchablehq/middleware"; ``` Wire them into any framework with an after-response hook. ### Express ```ts server.ts theme={null} import express from "express"; import { buildEventPayload, sendEvent, anonymizeIp, } from "@searchablehq/middleware"; const app = express(); app.use((req, res, next) => { const start = Date.now(); res.on("finish", () => { const captured = { domain: req.headers.host ?? "", method: req.method, url: `${req.protocol}://${req.headers.host}${req.originalUrl}`, path: req.path, status_code: res.statusCode, response_time_ms: Date.now() - start, user_agent: req.headers["user-agent"] ?? "", ip_address: anonymizeIp( (req.headers["x-forwarded-for"] as string)?.split(",")[0]?.trim() ?? req.ip ?? "", ), referrer: (req.headers.referer as string) ?? "", referrer_domain: "", headers: {}, query_parameters: {}, utm_source: "", utm_medium: "", utm_campaign: "", utm_term: "", utm_content: "", } satisfies CapturedRequest; const payload = buildEventPayload(captured, process.env.SEARCHABLE_SITE_TOKEN!); // Fire-and-forget — don't await void sendEvent(payload, { siteToken: process.env.SEARCHABLE_SITE_TOKEN!, apiKey: process.env.SEARCHABLE_API_KEY!, }); }); next(); }); ``` ### Fastify ```ts server.ts theme={null} import Fastify from "fastify"; import { buildEventPayload, sendEvent, anonymizeIp, } from "@searchablehq/middleware"; const app = Fastify(); app.addHook("onResponse", async (request, reply) => { const captured = { domain: request.headers.host ?? "", method: request.method, url: `${request.protocol}://${request.headers.host}${request.url}`, path: request.url.split("?")[0], status_code: reply.statusCode, response_time_ms: reply.elapsedTime, user_agent: request.headers["user-agent"] ?? "", ip_address: anonymizeIp(request.ip), referrer: (request.headers.referer as string) ?? "", referrer_domain: "", headers: {}, query_parameters: {}, utm_source: "", utm_medium: "", utm_campaign: "", utm_term: "", utm_content: "", }; const payload = buildEventPayload( captured, process.env.SEARCHABLE_SITE_TOKEN!, ); void sendEvent(payload, { siteToken: process.env.SEARCHABLE_SITE_TOKEN!, apiKey: process.env.SEARCHABLE_API_KEY!, }); }); ``` > [!TIP] > > Always call `sendEvent` after the response is finalised (`res.on("finish")`, `onResponse`, …). Doing it earlier slows down the response, and `status_code` / `response_time_ms` won't be accurate. ## What gets captured For every non-static request the SDK records: * HTTP method, path, URL, status code, response time * User agent (used by Searchable to classify the AI bot) * Anonymised IP (zero last octet by default — toggle via `anonymizeIp: false`) * Referrer + parsed referrer domain * UTM parameters extracted from the URL * Geo location (country, region, city) when your edge runtime exposes it (e.g. Next.js Edge Runtime) * Filtered request headers — only an allowlist of safe ones (`accept-language`, `host`, `sec-ch-ua*`, etc.) * Non-UTM query parameters * Anything you return from `custom(request)` Cookies, request/response bodies, and full IP addresses are never sent. Sensitive headers (`authorization`, `cookie`, `set-cookie`, `x-api-key`, `proxy-authorization`, `x-forwarded-for`, `x-real-ip`) are stripped at the edge before the worker forwards events to ingest. ## Verifying the connection In Searchable: 1. Go to **LLM Analytics → Setup** 2. The **Custom** card's status indicator should flip to **Connected** once the first event arrives 3. Hit your site with `curl` using a known AI user agent to force one: ```bash theme={null} curl -H "User-Agent: GPTBot/1.0 (+https://openai.com/gptbot)" \ https://yourdomain.com/ ``` | Status | What it means | | - | - | | **Waiting for first event** | The middleware is deployed but no AI bot has been seen yet. Curl an AI UA to force one. | | **Connected** | Events are arriving. The count from the last 24 hours is shown alongside. | Add `debug: true` locally to confirm events are firing without leaving your dev environment. ## Troubleshooting > **Status stays on 'Waiting for first event'** > > Most often the request never reaches Searchable because the middleware isn't running on the routes AI bots hit. > > * Check your `config.matcher` actually covers the live URLs — Next's default skips `_next/*` but you might be excluding more than intended > * Confirm `SEARCHABLE_SITE_TOKEN` and `SEARCHABLE_API_KEY` are set in the deployed environment (not just locally) > * Set `debug: true`, redeploy, and check your logs — the SDK logs every event it sends > * Curl your live domain with `User-Agent: GPTBot/1.0` and re-check the status > **I see logs but no events in the dashboard** > > Searchable filters non-bot user agents server-side. If your test request used a normal browser UA, it's discarded silently. Use a known AI UA: > > ```bash theme={null} > curl -H "User-Agent: GPTBot/1.0 (+https://openai.com/gptbot)" https://yourdomain.com/ > ``` > > Other supported test UAs: `ClaudeBot/1.0`, `PerplexityBot/1.0`, `Google-Extended/1.0`. > **The middleware throws 401 / 403 in logs** > > The API key is missing or wrong: > > * Confirm `SEARCHABLE_API_KEY` starts with `sk_live_` and has no leading/trailing whitespace > * If you've recently revoked the key in Searchable, generate a new one and update your env > * The key must have the **Log Events** permission. Re-create it from the Custom connector dialog if unsure — that's the default permission for keys generated there. > **I want to use a different site token per environment** > > Set different values for `SEARCHABLE_SITE_TOKEN` in your staging and production environments. Site tokens are per-project — staging traffic going to a staging project keeps your production data clean. > > The same workspace `SEARCHABLE_API_KEY` works across all projects in a workspace as long as it isn't project-scoped. To scope a key to a single project, generate it from inside that project's Custom connector dialog. > **My CDN strips the Authorization header** > > Override the endpoint via `endpoint` and point at a path your CDN forwards untouched, or route the SDK's POST through a server-side proxy that re-adds the header. ## Removing the integration 1. Delete the `withSearchable(...)` call (or pass through the inner middleware only) 2. Remove `SEARCHABLE_SITE_TOKEN` and `SEARCHABLE_API_KEY` from your env 3. In Searchable → **Settings → API Keys** → revoke the API key Revoking the key alone is enough to stop ingestion immediately — every in-flight POST starts failing with 403 — even if the SDK is still deployed. ## Next steps > **[REST API reference](/setup/rest-api)** > > Want the raw HTTP shape, or instrument a non-Node stack? > **[See the data](/using-searchable/visibility-tracking)** > > Open LLM Analytics to see which assistants are crawling your site. # Send Netlify traffic to Searchable (HTTP Log Drain) Source: https://docs.searchable.com/setup/netlify-drain Configure a Netlify HTTP Log Drain to stream request logs to Searchable. No code changes — all configuration is done inside the Netlify dashboard. ## What this does Netlify's HTTP Log Drain streams every inbound request log entry to a destination of your choosing. We point that drain at Searchable's ingest endpoint, classify the AI bots, and drop everything else. > [!NOTE] > > **No code changes.** All configuration is done inside the Netlify dashboard. > [!WARNING] > > **Enterprise plan only.** HTTP Log Drains are a Netlify Enterprise feature. If you're on a Free, Pro, or Business plan, use the **[Edge Function](/setup/netlify-edge)** path instead — it works on all plans. ## Prerequisites > [!TIP] > > A Netlify team on the Enterprise plan > [!TIP] > > Team Owner access in Netlify (Log Drain settings are team-level) > [!TIP] > > A Searchable project with your domain confirmed ## Setup **1. Generate an integration token in Searchable** 1. Open your Searchable dashboard 2. Go to **LLM Analytics → Setup** 3. Pick **Netlify Log Drain** as your crawler source 4. Click **Generate token** Copy the token now — it starts with `sa_…` and won't be shown again. You can always generate a new one if you lose it. **2. Open Netlify's Log Drain settings** Navigate to your Netlify team: **Team settings → Log drains → Add log drain** This setting is team-level, not site-level. You need Team Owner access to see it. **3. Configure the drain** Fill in these fields: | Field | Value | | - | - | | **Service** | HTTPS endpoint | | **Endpoint URL** | `https://tracker.searchableanalytics.com/v1/netlify-logs` | | **Format** | JSON (Netlify delivers NDJSON over HTTPS) | | **Custom header name** | `Authorization` | | **Custom header value** | `Bearer ` | > [!TIP] > > The custom header value must be `Bearer ` followed by the full `sa_…` token, with one space and no quotes. The header name must be exactly `Authorization`. **4. Save and verify** Click **Save**. Netlify sends a verification ping to the endpoint — Searchable accepts it. Then return to **LLM Analytics → Setup** in Searchable. The Netlify Log Drain card should show **Connected** within a few minutes once an AI bot hits your site. ## Verifying the connection In Searchable: 1. Go to **LLM Analytics → Setup** 2. Look at the Netlify Log Drain card status 3. Click **Check** if it still shows "Waiting for first event" | Status | What it means | | - | - | | **Waiting for first event** | The drain is configured but no AI bot has hit your site yet. Typical wait is a few hours for sites that are already indexed. | | **Connected** | Events are arriving. The card shows the count from the last 24 hours. | ## Troubleshooting > **Searchable shows 401 errors or the card stays 'Not connected'** > > The Authorization header is missing or wrong. > > * Make sure you added a **custom header** named exactly `Authorization` > * The value must be `Bearer ` followed by the full `sa_…` token, with one space and no quotes > * If you've recently revoked the token in Searchable, generate a new one and update the drain > **Netlify reports delivery errors** > > Check the drain format setting. > > * The format must be set to **JSON** (which delivers NDJSON over HTTPS) — not Datadog, Splunk, or any other structured format > * If the format was wrong, update it and save — Netlify will retry pending deliveries > **I don't see the Log Drain option in Netlify** > > Log Drains are only visible to Team Owners on the Enterprise plan. If you don't see **Team settings → Log drains**: > > * Confirm you have Team Owner access (not just Member) > * Confirm your Netlify team is on the Enterprise plan > > If you're not on Enterprise, use the **[Edge Function](/setup/netlify-edge)** path — it works on all plans and requires no Netlify plan upgrade. > **Status stays on 'Waiting for first event' for more than 24 hours** > > A few possible causes: > > * The drain isn't enabled or is paused — check Netlify's Team settings → Log drains > * Your domain in Searchable doesn't match the site on Netlify (check **LLM Analytics → Setup → Confirm your domain**) > * The drain is configured but no AI bot has visited yet — try visiting your site with a known AI user agent to trigger a test event > > If deliveries are succeeding in Netlify but nothing appears in Searchable, that points to a domain mismatch. ## Removing the integration To stop sending traffic to Searchable: 1. Netlify → **Team settings → Log drains** → delete the drain 2. Searchable → **LLM Analytics → Setup → Tokens** → revoke the token Both sides are independent — revoking the token alone is enough to stop ingestion immediately, even if the drain stays configured in Netlify (its deliveries will start returning `401`). ## Next steps > **[See the data](/using-searchable/visibility-tracking)** > > Open LLM Analytics to see which assistants are crawling your site. > **[Add Search Console](/integrations/google-search-console)** > > Correlate AI crawls with search demand. # Send Netlify traffic to Searchable (Edge Function) Source: https://docs.searchable.com/setup/netlify-edge Forward AI-bot traffic from your Netlify-hosted site to Searchable using a small Edge Function that runs on every request. Works on every Netlify plan, including Free. ## What this does An Edge Function runs at Netlify's edge in front of your origin. For every inbound request it: 1. Lets the request pass through to your origin unchanged (no added latency for users) 2. After the response is returned, fires a fire-and-forget POST to Searchable with the request metadata 3. Returns the original response to the visitor Searchable classifies the user agent server-side and records it if it matches a known AI crawler. > [!NOTE] > > **Works on every Netlify plan, including Free.** Netlify's free plan covers 1M Edge Function > invocations per month — well above typical bot traffic for most sites. ## Prerequisites > [!TIP] > > A Netlify site with deploy access > [!TIP] > > A Searchable project with your domain confirmed ## Setup **1. Generate an integration token in Searchable** 1. Open your Searchable dashboard 2. Go to **LLM Analytics → Setup** 3. Pick **Netlify Edge Function** as your crawler source 4. Click **Generate token** Copy the token now — it starts with `sa_…` and won't be shown again. You can always generate a new one if you lose it. **2. Add the Edge Function file to your repo** Create the file `netlify/edge-functions/searchable.ts` in your repository and paste the snippet below. ```typescript netlify/edge-functions/searchable.ts theme={null} import type { Context } from "https://edge.netlify.com"; const ENDPOINT = "https://tracker.searchableanalytics.com/v1/netlify-edge"; const TOKEN = Netlify.env.get("SEARCHABLE_TOKEN"); export default async function (request: Request, context: Context) { const response = await context.next(); context.waitUntil(forward(request, response, context)); return response; } async function forward(request: Request, response: Response, context: Context) { if (!TOKEN) return; try { const url = new URL(request.url); await fetch(ENDPOINT, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${TOKEN}` }, body: JSON.stringify({ type: "netlify_edge_event", timestamp: Date.now(), request_id: request.headers.get("x-nf-request-id") ?? crypto.randomUUID(), method: request.method, path: url.pathname, url: request.url, status_code: response.status, user_agent: request.headers.get("user-agent") ?? "", ip_address: context.ip ?? "", country: context.geo?.country?.code ?? "", referrer: request.headers.get("referer") ?? "", }), }); } catch { /* swallow */ } } export const config = { path: "/*" }; ``` The function intercepts every request, forwards a lightweight event to Searchable after the response is returned, and never blocks the user. **3. Add the environment variable in Netlify** In your Netlify site: **Site settings → Environment variables → Add a variable** | Key | Value | | - | - | | `SEARCHABLE_TOKEN` | The `sa_…` token you generated above | Make sure the variable name is exactly `SEARCHABLE_TOKEN` (all caps, underscore). **4. Commit and push** Commit the new file and push to your main branch. Netlify will auto-deploy the edge function. ```bash theme={null} git add netlify/edge-functions/searchable.ts git commit -m "Add Searchable edge function" git push ``` **5. Verify the function is active** In Netlify, open **Site settings → Edge functions** and confirm that `searchable` appears in the list and is marked active. ## Verifying the connection Return to **LLM Analytics → Setup** in your Searchable dashboard. The Netlify Edge Function card should flip to **Connected** within about 10 seconds of the next AI crawler hitting your site. | Status | What it means | | - | - | | **Waiting for first event** | The function is deployed but no AI bot has hit your site yet. Typical wait is a few hours for sites that are already indexed. | | **Connected** | Events are arriving. The card shows the count from the last 24 hours. | ## Cost Each forwarded request consumes one Edge Function invocation. Netlify's free plan covers 1M invocations per month — bot traffic is a tiny fraction of that for typical sites. ## Troubleshooting > **Card stays 'Not connected' after deploying** > > Check these in order: > > * The environment variable is named exactly `SEARCHABLE_TOKEN` (all caps, underscore — not camelCase or hyphenated) > * The deploy succeeded — check Netlify's deploy log for errors in the edge function > * The function is active in **Site settings → Edge functions** > * The `config` export at the bottom of the file sets `path: "/*"` so the function runs on all requests > **I only see human traffic, not bots** > > That's expected. Searchable filters non-bot user agents server-side — only AI crawlers (GPTBot, > ClaudeBot, PerplexityBot, etc.) are recorded in your dashboard. Human visitors are forwarded but > immediately discarded. > **Netlify logs show errors in the edge function** > > If the function throws during `context.next()`, Netlify may surface it in deploy logs. The `forward()` call is wrapped in a `try/catch` and uses `context.waitUntil(...)`, so errors inside `forward` are silently swallowed and never affect your visitors. > > Confirm the function file is valid TypeScript and that the `import type { Context }` line is present — Netlify's Deno runtime requires it. ## Removing the integration 1. Delete `netlify/edge-functions/searchable.ts` from your repo and push 2. Netlify → **Site settings → Environment variables** → remove `SEARCHABLE_TOKEN` 3. Searchable → **LLM Analytics → Setup → Tokens** → revoke the token ## Next steps > **[See the data](/using-searchable/visibility-tracking)** > > Open LLM Analytics to see which assistants are crawling your site. > **[Add Search Console](/integrations/google-search-console)** > > Correlate AI crawls with search demand. # LLM Analytics Setup Source: https://docs.searchable.com/setup/overview Connect your hosting platform to Searchable so we can show you which AI assistants and crawlers are visiting your site ## What this does Searchable's LLM Analytics dashboard shows you, in near real-time, which AI assistants — ChatGPT, Claude, Perplexity, Google's AI Overviews, and the rest — are crawling your site, what pages they're hitting, and which of those crawls turn into referred users. To do that, Searchable needs to see your inbound HTTP requests. The setup below points your hosting platform's request logs at our ingest endpoint. We classify the AI bots, drop everything else, and never store query strings or other PII. > [!NOTE] > > Setup takes about 5 minutes. There are no agents to install and no code to change in your > application. > [!TIP] > > Would rather not test this against production first? Register a **[staging > site](/setup/staging)**, follow your platform's guide against that, and confirm events arrive > before you touch production. Staging traffic is reported separately and never counts toward your > production numbers. ## Pick your platform > **[Vercel](/setup/vercel)** > > One-click Log Drain. Works on Pro and Enterprise. > **[Cloudflare Worker](/setup/cloudflare-worker)** > > Edge Worker — works on any Cloudflare plan, including Free. > **[Cloudflare Logpush](/setup/cloudflare)** > > Native Logpush job — Enterprise plan only. > **[Amazon CloudFront](/setup/cloudfront)** > > Standard Logging V2 via Firehose — any CloudFront distribution. > **[Netlify Edge Function](/setup/netlify-edge)** > > Edge Function — works on every Netlify plan, including Free. > **[Netlify Log Drain](/setup/netlify-drain)** > > HTTP Log Drain — Enterprise plan only. > **[Fastly](/setup/fastly-logs)** > > Real-Time Log Streaming via HTTPS endpoint. > **[Akamai](/setup/akamai-datastream2)** > > DataStream 2 HTTPS push — any property with DataStream 2 entitled. > **[Google Cloud Platform](/setup/gcp-logs)** > > HTTPS Load Balancer / Cloud CDN logs via Pub/Sub push. > **[WordPress](/setup/wordpress)** > > Must-use plugin — captures AI crawlers at the origin on any self-hosted site. > **[Other platforms](/setup/custom)** > > Custom REST API or middleware for everything else. ## Which one should I use? > **I host on Vercel** > > Use **[Vercel Log Drain](/setup/vercel)**. It's the most direct path — Vercel streams request logs to Searchable with one entry in your project settings. No code changes. > **I'm on Cloudflare (any plan)** > > Use the **[Cloudflare Worker](/setup/cloudflare-worker)**. It works on every Cloudflare plan > including Free, and the Free tier easily covers \~100k requests/day. The Worker forwards AI-bot > traffic to Searchable from the edge — no latency added to real users. > **I'm on Cloudflare Enterprise** > > You can use either path. **[Cloudflare Logpush](/setup/cloudflare)** is the most "native" option — > Cloudflare batches and ships logs directly with no Worker involved. If you'd rather avoid the > Enterprise-only Logpush feature, the **[Worker](/setup/cloudflare-worker)** path works on > Enterprise too. > **I'm on Amazon CloudFront** > > Use **[Amazon CloudFront → Firehose](/setup/cloudfront)**. CloudFront's free Standard Logging V2 > feature ships records to Amazon Data Firehose, which forwards them to Searchable. Works on any > CloudFront distribution — you only pay for the Firehose ingestion. > **I host on Netlify** > > Use the **[Netlify Edge Function](/setup/netlify-edge)** on any plan (including Free), or > **[Netlify Log Drain](/setup/netlify-drain)** if you're on the Enterprise plan. The Edge Function > path runs at Netlify's edge with no added latency for real users. > **I host on Fastly** > > Use **[Fastly Real-Time Log Streaming](/setup/fastly-logs)**. Configure an HTTPS Logging endpoint > inside the Fastly UI — no code changes — and Fastly streams request logs to Searchable. > **I'm on Akamai** > > Use **[Akamai DataStream 2](/setup/akamai-datastream2)**. Create a DataStream 2 stream in Akamai > Control Center, pick the property fronting your domain, and point it at Searchable's HTTPS > endpoint. Requires the DataStream 2 product on your Akamai contract — your account team can > confirm if it's enabled. > **I host on Google Cloud Platform** > > Use **[Google Cloud → Pub/Sub push](/setup/gcp-logs)**. Cloud Logging captures every HTTPS Load > Balancer or Cloud CDN request; a Pub/Sub topic with a push subscription forwards those logs to > Searchable. > **I run WordPress** > > Use the **[WordPress plugin](/setup/wordpress)** — a must-use plugin that forwards AI-crawler > requests from the origin, with no theme changes and no JavaScript. One caveat: if your site is > behind Cloudflare, or runs a full-page cache that serves HTML without booting PHP, prefer the > **[Cloudflare Worker](/setup/cloudflare-worker)** — it sits in front of the cache and sees every > request. > **I'm somewhere else (other platforms, custom)** > > Use the **[custom integration](/setup/custom)**. Searchable accepts a small REST POST from your application middleware or any platform that can ship structured request logs. ## What gets sent to Searchable For each request that matches an AI-bot user agent, we receive: * HTTP method, path, and host (no query strings) * User agent * Referer * Country code (geo-IP, no precise location) * Response status and bytes * Timestamp That's it. Query strings, request bodies, response bodies, headers other than `User-Agent` / `Referer`, and full IP addresses are never sent. ## Verifying the connection After completing setup, return to **LLM Analytics → Setup** in your Searchable dashboard. The status strip at the bottom updates as events arrive: 1. **Waiting for first event** — your platform hasn't sent anything yet. AI bots typically hit any indexed site within a few hours. 2. **Connected** — events are flowing. The strip shows the count from the last 24 hours. If the status doesn't update within a few hours, see the troubleshooting section in the platform-specific guide you followed. ## Next steps > **[Add a domain](/getting-started/creating-your-project)** > > LLM Analytics is scoped to your project's primary domain. > **[Connect Search Console](/integrations/google-search-console)** > > Layer in keyword data so you can correlate AI crawls with search demand. # Send traffic to Searchable via the REST API Source: https://docs.searchable.com/setup/rest-api Send AI-bot traffic events to Searchable from any language or platform — a single HTTP POST with a signed Bearer token. ## What this is Searchable's REST API is the language-agnostic way to ship request events from your app. It's a single `POST` to a Cloudflare-hosted ingest endpoint with an `Authorization: Bearer sk_live_…` header and a JSON body of one or more events. The server-side AI-bot classifier filters non-AI user agents, so even if you POST every request from your app, only crawlers like GPTBot, ClaudeBot, and PerplexityBot end up in your dashboard. > [!NOTE] > > **Use this when the [Middleware SDK](/setup/middleware) doesn't fit** — non-Node stacks, in-house > CDN workers, batch jobs that replay logs, or anywhere you want fine-grained control over the > payload. ## Prerequisites > [!TIP] > > The two credentials from the [common prerequisites](/setup/custom#common-prerequisites): a project > **site token** (`st_…`) and a workspace **API key** (`sk_live_…`) > [!TIP] > > Any runtime that can make an authenticated HTTPS POST — `curl`, `fetch`, `requests`, Go's > `net/http`, etc. ## Endpoint ``` POST https://tracker.searchableanalytics.com/v1/events ``` Requests are authenticated, verified, and forwarded at Cloudflare's edge — there's no DB round-trip on the auth path, so you can call this from latency-sensitive contexts. | Header | Value | | - | - | | `Authorization` | `Bearer sk_live_…` — the workspace API key | | `Content-Type` | `application/json` | ## Request body ```json theme={null} { "site_token": "st_your_token_here", "events": [ { "event_name": "server_request", "timestamp": 1716105600000, "method": "GET", "path": "/blog/my-post", "url": "https://example.com/blog/my-post", "status_code": 200, "response_time_ms": 42, "user_agent": "GPTBot/1.0", "referrer": "", "ip_address": "203.0.113.42", "country": "US" } ] } ``` ### Top-level fields | Field | Type | Required | Description | | - | - | - | - | | `site_token` | string | yes | The project's private site token (starts with `st_`) | | `events` | array | yes | One or more event objects. Batch up to 1MB of payload per request. | ### Event fields | Field | Type | Required | Description | | - | - | - | - | | `event_name` | string | yes | Use `"server_request"` for HTTP request events | | `timestamp` | number | yes | Unix timestamp in **milliseconds** | | `method` | string | yes | HTTP method (`GET`, `POST`, …) | | `path` | string | yes | Request path. Query strings are stripped server-side. | | `url` | string | yes | Full request URL | | `status_code` | number | yes | HTTP response status | | `response_time_ms` | number | yes | Server response time in milliseconds | | `user_agent` | string | — | User-Agent header. Empty string if unavailable. | | `ip_address` | string | — | Client IP. Defaults to `0.0.0.0` if omitted. We recommend anonymizing the last octet client-side. | | `referrer` | string | — | HTTP Referer header | | `referrer_domain` | string | — | Pre-parsed referrer hostname (we'll derive it from `referrer` if omitted) | | `country` | string | — | 2-letter ISO country code (e.g. `US`). Geo enrichment otherwise comes from Cloudflare's edge metadata. | | `region` | string | — | Region / state code | | `city` | string | — | City name | | `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content` | string | — | Standard UTM fields | | `headers` | object | — | Map of safe request headers you want to retain | | `query_parameters` | object | — | Non-UTM query parameters as a string map | | `custom_properties` | object | — | Arbitrary string-keyed properties exposed as `parameters` in the dashboard | Unknown fields are ignored. An optional field sent as `null` is treated as omitted. Fields with bad types are coerced where safe (counts → unsigned int, status codes → uint). ## Response | Status | Meaning | | - | - | | `202 Accepted` | Events were accepted and forwarded to ingest. The body is empty. | | `400 Bad Request` | Body wasn't valid JSON, or `events` wasn't an array. | | `401 Unauthorized` | Missing `Authorization` header. | | `403 Forbidden` | Token signature invalid, or `site_token` missing from body. | | `413 Payload Too Large` | Request body exceeded 1 MB. Split your batch. | `202` is the success status — events are accepted and dispatched asynchronously. There is no synchronous confirmation that an event reached ClickHouse; use the **LLM Analytics → Setup** status strip to verify end-to-end flow. ## Quick start — `curl` ```bash theme={null} curl -X POST https://tracker.searchableanalytics.com/v1/events \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "site_token": "st_YOUR_SITE_TOKEN", "events": [{ "event_name": "server_request", "timestamp": '"$(date +%s%3N)"', "method": "GET", "path": "/blog/my-post", "url": "https://example.com/blog/my-post", "status_code": 200, "response_time_ms": 42, "user_agent": "GPTBot/1.0" }] }' ``` A successful call returns an empty body and `202 Accepted`. Within a few seconds, the **Custom** card in **LLM Analytics → Setup** flips to **Connected**. ## Examples ### Node — `fetch` ```ts theme={null} async function reportRequest(req: { method: string; path: string; url: string; status: number; durationMs: number; userAgent: string; }) { await fetch("https://tracker.searchableanalytics.com/v1/events", { method: "POST", headers: { Authorization: `Bearer ${process.env.SEARCHABLE_API_KEY!}`, "Content-Type": "application/json", }, body: JSON.stringify({ site_token: process.env.SEARCHABLE_SITE_TOKEN!, events: [ { event_name: "server_request", timestamp: Date.now(), method: req.method, path: req.path, url: req.url, status_code: req.status, response_time_ms: req.durationMs, user_agent: req.userAgent, }, ], }), }).catch(() => { // Fire-and-forget. Never fail the user's request on a Searchable error. }); } ``` Don't `await` this from a request handler in latency-sensitive paths — either fire it after the response is flushed, or push it onto a worker queue. ### Python — `requests` ```python theme={null} import os import time import requests def report_request(method, path, url, status, duration_ms, user_agent): payload = { "site_token": os.environ["SEARCHABLE_SITE_TOKEN"], "events": [{ "event_name": "server_request", "timestamp": int(time.time() * 1000), "method": method, "path": path, "url": url, "status_code": status, "response_time_ms": duration_ms, "user_agent": user_agent, }], } try: requests.post( "https://tracker.searchableanalytics.com/v1/events", json=payload, headers={"Authorization": f"Bearer {os.environ['SEARCHABLE_API_KEY']}"}, timeout=2, ) except requests.RequestException: pass # fire-and-forget ``` ### Go — `net/http` ```go theme={null} package main import ( "bytes" "context" "encoding/json" "net/http" "os" "time" ) type Event struct { EventName string `json:"event_name"` Timestamp int64 `json:"timestamp"` Method string `json:"method"` Path string `json:"path"` URL string `json:"url"` StatusCode int `json:"status_code"` ResponseTimeMs int `json:"response_time_ms"` UserAgent string `json:"user_agent"` } type Payload struct { SiteToken string `json:"site_token"` Events []Event `json:"events"` } func Report(ctx context.Context, e Event) error { body, _ := json.Marshal(Payload{ SiteToken: os.Getenv("SEARCHABLE_SITE_TOKEN"), Events: []Event{e}, }) req, _ := http.NewRequestWithContext(ctx, "POST", "https://tracker.searchableanalytics.com/v1/events", bytes.NewReader(body)) req.Header.Set("Authorization", "Bearer "+os.Getenv("SEARCHABLE_API_KEY")) req.Header.Set("Content-Type", "application/json") client := &http.Client{Timeout: 2 * time.Second} resp, err := client.Do(req) if err != nil { return err } resp.Body.Close() return nil } ``` ## Batching The endpoint accepts up to 1 MB per request and any number of events in the `events` array. For high-volume sources, batch events on a short flush interval (e.g. every 5 s or every 100 events) rather than sending one POST per request — fewer network round-trips, same data. Each event in a batch is independent; ingest validates and persists them individually, so a single malformed event doesn't drop the rest of the batch. ## What gets recorded Even though you can post any HTTP request through this endpoint, Searchable's server-side classifier only records events whose `user_agent` matches a known AI crawler. Everything else is dropped silently. That's intentional: it lets you instrument your app once and not worry about which UAs to filter in your code. The bot list is refreshed daily — new AI agents are picked up automatically. You can sanity-check the classifier against the public bot artifact at: ``` GET https://tracker.searchableanalytics.com/v1/bots.json ``` That's the same list the worker uses internally — useful if you want to do client-side filtering to reduce ingest load. ## Verifying the connection In Searchable: 1. Go to **LLM Analytics → Setup** 2. Hit the endpoint with a known AI user agent to force a first event (see the [`curl` example](#quick-start-curl)) 3. Click **Refresh** in the status strip | Status | What it means | | - | - | | **Waiting for first event** | API key + body are valid but no event has matched the bot classifier yet. | | **Connected** | Events are arriving. The strip shows the count from the last 24 hours. | ## Troubleshooting > **401 Unauthorized** > > The `Authorization` header is missing. > > * Check the header is named exactly `Authorization` (not `authorisation`, `X-Authorization`, etc.) > * The value must be `Bearer ` followed by your `sk_live_…` key, with one space and no quotes > * Some CDNs strip `Authorization` on internal hops — verify the header is present when the request leaves your edge > **403 Forbidden — `invalid_signature`** > > The API key's signature failed verification. > > * Confirm you copied the key in full — it's two URL-safe base64 segments separated by a `.` > * If you've recently revoked the key in Searchable, generate a new one (Settings → API Keys → New key) > * Make sure you're using a key with the **Log Events** permission — generating from the Custom connector dialog assigns it by default > **403 Forbidden — `missing_site_token`** > > The body must include `site_token` at the top level. Don't put it inside an event object. > > ```json theme={null} > { "site_token": "st_…", "events": [ /* … */ ] } > ``` > **400 Bad Request — `invalid_events_array`** > > `events` must be an array, even for a single event. Wrap your event in `[ … ]`: > > ```json theme={null} > { "site_token": "st_…", "events": [ { /* event */ } ] } > ``` > **413 Payload Too Large** > > Request body exceeded 1 MB. Either split into multiple POSTs, or trim large fields (headers, query > parameters, custom properties) from each event. > **Events return 202 but never appear in the dashboard** > > The classifier is dropping them because the `user_agent` isn't a known AI crawler. Use an AI UA in your test: > > ```bash theme={null} > curl -H "User-Agent: GPTBot/1.0 (+https://openai.com/gptbot)" ... > ``` > > Or fetch the live AI-bot list and confirm your UA matches one of the patterns: > > ```bash theme={null} > curl https://tracker.searchableanalytics.com/v1/bots.json > ``` ## Removing the integration 1. Stop sending POSTs from your app 2. In Searchable → **Settings → API Keys** → revoke the API key Revoking the key is the cleanest stop — every subsequent POST returns 403, regardless of where it's coming from. ## Next steps > **[Middleware SDK](/setup/middleware)** > > On Node? The SDK is one import and handles the payload for you. > **[See the data](/using-searchable/visibility-tracking)** > > Open LLM Analytics to see which assistants are crawling your site. # Test your Searchable integration on a staging site Source: https://docs.searchable.com/setup/staging Prove a crawler-log integration works against a staging hostname before you point production at it — staging traffic is reported separately and never counts toward your production analytics ## What this does Every setup guide in this section ends the same way: you wire up a log drain, an edge function or a REST call, and then wait to see whether events arrive. Doing that against production means your first attempt is also your first live change. A **staging site** lets you do it somewhere safe. You register a non-production hostname — `staging.example.com`, `preprod.example.com` — point an integration at it, and confirm events land. The traffic is reported separately, and none of it reaches your production numbers. > [!NOTE] > > Staging sites are for a **stable** hostname you control, not for per-deploy preview URLs. More on > that in [Limits](#limits) below. ## Prerequisites > [!TIP] > > A Searchable project with LLM Analytics set up for your production domain > [!TIP] > > A stable staging hostname that is already serving traffic > [!TIP] > > Permission to configure the integration on that host (the same access the platform guide for your > host asks for) > [!NOTE] > > Staging sites are still rolling out. If the **Staging site** card isn't on your Setup tab yet, > your account doesn't have it — nothing else in this section depends on it, so every platform guide > works as written against your production site in the meantime. ## Setup **1. Add the staging site** In your Searchable dashboard, go to **LLM Analytics → Setup** and find the **Staging site** card. Click **Add staging site**, then fill in: | Field | Value | | - | - | | **Staging hostname** | The host as it appears in requests, e.g. `staging.example.com` | | **Site name** | Optional label, e.g. `Staging`. Only affects how it reads in the site picker. | Adding a staging site turns **Development mode** on for you automatically, which is what makes it visible in the site pickers. **2. Select the staging site** Still on the Setup tab, use the **Site** selector to switch to the staging site. Staging sites are grouped together at the top of that list, each marked **Staging**. A note appears confirming you're configuring a staging environment. That note is your signal that everything you do next belongs to staging rather than production. **3. Follow your platform's guide as normal** With the staging site selected, pick your crawler source and follow the guide for your host exactly as you would for production — see the [setup overview](/setup/overview) to choose one. The important part is that the integration token you generate while a staging site is selected **belongs to that site**. There is nothing extra to configure: point the drain, function or REST call at the token you just copied, and its events are attributed to staging. > [!WARNING] > > Generate the token *after* switching sites. A token minted while production was selected sends > its events to production, however staging-looking the hostname is — tokens are what decide > attribution here, not the hostname in the request. **4. Send some traffic** Hit the staging host and watch the events arrive under the staging scope (next section). You do not need to wait for a real AI crawler: any request the integration forwards is enough to prove the wiring, because what you are testing is the path from your host to Searchable. ## Seeing staging data Staging numbers are never mixed into the dashboard you normally look at. To see them: 1. Open any LLM Analytics tab 2. Open the **Sites** filter and turn on **Development mode** 3. Pick the staging site from the **Staging** section at the top While a staging site is selected, an amber banner sits above the tabs for the whole visit, saying the numbers below it are staging data. Every figure on the page — crawls, pages, referred visitors — is then scoped to staging only. > [!NOTE] > > **Development mode is yours alone.** It is stored in your own browser, per project, so turning it > on does not change what a colleague sees. Someone else on the project has to turn it on themselves > before staging appears in their pickers. Nothing about the data is hidden from them — only the > picker entry. ## What stays separate Three guarantees are worth being explicit about, because they are what make a staging site safe to leave in place rather than something to tear down afterwards: * **Production totals never move.** Staging traffic is excluded from every production figure on the project. Selecting the staging scope is the only way to see it. * **A staging site is yours, not shared.** It belongs to exactly one project and carries its own token. Two different projects can each register the same staging hostname and neither can see the other's data. * **There is no ownership check to pass.** A production domain has to prove ownership before it will report, because the same production hostname can be tracked by more than one project. A staging site already belongs to exactly one project, so there is nothing to adjudicate: no DNS record, no waiting. That is what makes this work on hosts whose DNS you do not control. ## Limits > **Per-deploy preview URLs won't work** > > A hostname that changes every build — `acme-git-feat-x.vercel.app` and friends — cannot be > registered usefully, because the host you register has to be the host that appears in requests. > Point a stable alias at your preview environment and register that instead. > **A staging site's hostname can't be changed later** > > The hostname is the staging site's identity, so it is fixed once added. The optional **site name** > is just a label and can be edited freely. Choose the hostname deliberately. > **A staging site can't be promoted to production** > > Staging and production are separate records with separate history, so there is no switch that > turns one into the other. When you're ready for production, follow your platform's guide again > with the production site selected. The staging history stays where it is. > **Staging data is not compared against production for you** > > There is no built-in parity view that diffs staging against production. Both scopes show the same > metrics, so you can compare them side by side yourself by switching the site filter. ## Verifying it worked Select the staging site in the dashboard, as above. Events arriving under the staging scope — with your production totals unchanged — is the confirmation that the integration is wired correctly. | What you see | What it means | | - | - | | Events under the **staging** scope | The integration works. The same guide against your production site will behave the same way. | | Nothing under staging, but production moved | The token belongs to your production site. See the first troubleshooting item below. | | Nothing under either scope | The integration isn't delivering yet. Use the troubleshooting section of your platform's guide — the wiring problem is on the host side, not specific to staging. | ## Troubleshooting > **My staging events are showing up in production** > > The token was generated while the production site was selected, so that is where its events go. > > Switch the **Site** selector to the staging site, generate a **new** token there, and update your > integration to use it. Then revoke the old one so the earlier wiring can't keep reporting. > **The staging site isn't in the site picker** > > **Development mode** is off. It's per person and per browser, so this is expected on a second > machine, in a private window, or for a colleague who has never turned it on. > > Turn it on from the **Site** selector on the Setup tab, or from the **Sites** filter on any > dashboard tab. > **I registered the wrong hostname** > > The hostname can't be edited after the fact. Add a second staging site with the correct hostname > and point your integration's token at that one — a project can hold more than one. > > The unused site simply stops receiving events once nothing is pointed at it. Revoke its token so > nothing can report to it by accident. > **I want to stop collecting staging traffic** > > Revoke the staging site's integration token. That stops ingestion immediately, whatever is still > configured on the host side — deliveries from the old wiring start failing authentication. > > Turning Development mode off only hides the site from your pickers; it does not stop collection. ## Next steps > **[Pick your platform](/setup/overview)** > > Choose the crawler source for your host and follow its guide against the staging site. > **[REST API](/setup/rest-api)** > > Send events yourself from any platform that can make an HTTP request. # Send Vercel traffic to Searchable Source: https://docs.searchable.com/setup/vercel Stream your Vercel project's request logs to Searchable using a Log Drain — no code changes, takes about 3 minutes ## What this does Vercel's [Log Drain](https://vercel.com/docs/observability/log-drains) feature streams every inbound request to a destination of your choosing. We point that drain at Searchable's ingest endpoint, classify the AI bots, and drop everything else. > [!NOTE] > > **No code changes.** All configuration is done inside the Vercel dashboard. ## Prerequisites > [!TIP] > > A Vercel project on the Pro or Enterprise plan (Log Drains aren't available on Hobby) > [!TIP] > > Owner or Member-with-admin access to the Vercel project > [!TIP] > > A Searchable project with your domain confirmed ## Setup **1. Generate an integration token in Searchable** 1. Open your Searchable dashboard 2. Go to **LLM Analytics → Setup** 3. Pick **Vercel** as your crawler source 4. Click **Generate token** Copy the token now — it starts with `sa_…` and won't be shown again. You can always generate a new one if you lose it. **2. Open Vercel's Log Drain settings** Navigate to your Vercel project: **Project Settings → Log Drains → Add** Or open it directly: [vercel.com/dashboard/log-drains](https://vercel.com/dashboard/log-drains) **3. Configure the drain** Fill in these fields: | Field | Value | | - | - | | **Delivery format** | NDJSON | | **Endpoint** | `https://tracker.searchableanalytics.com/v1/vercel-logs` | | **Custom headers** | `Authorization: Bearer ` | | **Sources** | Functions, Edge Functions, Static Files, Firewall, Redirects | | **Sampling rate** | 100% | > [!TIP] > > Selecting all five sources is what gives Searchable a complete picture. AI crawlers hit static files (sitemaps, robots.txt) almost as often as they hit pages. **4. Save and verify** Click **Save**. Vercel sends a verification ping to the endpoint immediately — Searchable accepts it. Then return to **LLM Analytics → Setup** in Searchable. The status strip at the bottom should show **Connected** within a few minutes once an AI bot hits your site. ## What Searchable receives For each request, the Log Drain sends a small NDJSON record with: * HTTP method, path, host (query strings stripped before storage) * User agent * Referer * Status code, response bytes * Timestamp * Geo country (from Vercel's edge metadata) Bodies, headers, cookies, and full IPs are never sent or stored. ## Verifying the connection In Searchable: 1. Go to **LLM Analytics → Setup** 2. Look at the status strip at the bottom of the page 3. Click **Check** if it still shows "Waiting for first event" | Status | What it means | | - | - | | **Waiting for first event** | The drain is configured but no AI bot has hit your site yet. Typical wait is a few hours for sites that are already indexed. | | **Connected** | Events are arriving. The strip shows the count from the last 24 hours. | You can also confirm in Vercel: **Log Drains → your drain → Deliveries**. Successful deliveries return `204 No Content`. ## Troubleshooting > **Vercel reports `401 Unauthorized` deliveries** > > The Authorization header is missing or wrong. > > * Make sure you added a **custom header** (not a query parameter) named exactly `Authorization` > * The value must be `Bearer ` followed by the full `sa_…` token, with one space and no quotes > * If you've recently revoked the token in Searchable, generate a new one and update the drain > **Vercel reports `400 Bad Request` deliveries** > > The delivery format isn't NDJSON. > > * In the drain settings, set **Delivery format** to `NDJSON` (not JSON or Logfmt) > * Save the drain — Vercel will retry pending deliveries > **Status stays on 'Waiting for first event' for more than 24 hours** > > A few possible causes: > > * The drain isn't enabled (check Vercel — drains can be paused) > * The selected sources don't include the routes AI bots are hitting (re-check **Sources** includes Static Files and Functions) > * Your domain in Searchable doesn't match the project on Vercel (check **LLM Analytics → Setup → Confirm your domain**) > > If everything looks right, Vercel's **Deliveries** tab tells you whether deliveries are succeeding. Successful deliveries that don't appear in Searchable point to a domain mismatch. > **My plan is Hobby — can I still use this?** > > Log Drains are a Pro / Enterprise feature on Vercel. On Hobby, your options are: > > * Upgrade Vercel to Pro > * Use the **[Cloudflare Worker](/setup/cloudflare-worker)** path if your domain sits behind Cloudflare > * Use the **[custom REST API](/setup/custom)** path with Next.js middleware ## Removing the integration To stop sending traffic to Searchable: 1. Vercel → **Project Settings → Log Drains** → delete the drain 2. Searchable → **LLM Analytics → Setup → Tokens** → revoke the token Both sides are independent — revoking the token alone is enough to stop ingestion immediately, even if the drain stays configured in Vercel (its deliveries will start returning `401`). ## Next steps > **[See the data](/using-searchable/visibility-tracking)** > > Open LLM Analytics to see which assistants are crawling your site. > **[Add Search Console](/integrations/google-search-console)** > > Correlate AI crawls with search demand. # Connect WordPress to Searchable Source: https://docs.searchable.com/setup/wordpress Install the Searchable must-use plugin on your WordPress site to forward AI-crawler requests to LLM Analytics — no theme changes, no JavaScript. ## What this is A small **must-use plugin** that runs inside WordPress, inspects the user agent of every request that reaches PHP, and forwards the ones that match Searchable's canonical AI-crawler list to LLM Analytics. It feeds the same server-side pipeline as the Vercel, Cloudflare, and Netlify connectors — same classifier, same dashboards. The difference is where it runs: in your application, not at a CDN edge. > [!NOTE] > > Must-use plugins load automatically and can't be deactivated from the admin UI, so the forwarder > survives plugin and theme updates. ## First, check this is the right path > [!WARNING] > > **If your site sits behind Cloudflare, use the [Cloudflare Worker](/setup/cloudflare-worker) > instead.** It runs at the edge, so it sees every AI-crawler request — including the ones a > full-page cache serves without ever booting PHP. It works on every Cloudflare plan including Free, > and it's less to maintain. Use the WordPress plugin when your site **isn't** behind a CDN we support natively, or when you specifically want the forwarder in the application layer. The same caveat applies to any full-page cache. See [Troubleshooting](#troubleshooting) below. ## Before you start > [!TIP] > > Self-hosted WordPress (this does not work on WordPress.com's hosted plans, which don't allow > `mu-plugins` or `wp-config.php` edits) > [!TIP] > > SFTP, SSH, or a file manager that can write to `wp-content/` and `wp-config.php` > [!TIP] > > Your project's domain confirmed in **LLM Analytics → Setup → Confirm your domain** LLM Analytics is available on every plan — no upgrade required. ## Step 1: Open the WordPress connector **1. Go to the Setup tab** In Searchable, open **LLM Analytics** (left nav, under Analytics) → **Setup**, then pick **WordPress** under "Connect your crawler source". **2. Generate a tracker API key** Click **Generate key**. Searchable mints a key with the `log_events` permission, scoped to this project, and shows it **once** — copy it now. Keys start with `sk_live_…`. > [!WARNING] > > The key is a live write credential. Treat it like a password. If you lose it, revoke and regenerate from the same panel or **Settings → API Keys**. **3. Copy the wp-config snippet** The panel shows a `define()` block with your project's site token already filled in. Copy it verbatim — you'll paste it in Step 3. ## Step 2: Install the plugin **1. Download the plugin file** Click **Download plugin** in the WordPress panel. It downloads `searchable-ai-traffic.php`, served from your project's tracker host. > [!TIP] > > Download from the button rather than reusing a copy from a colleague. The artifact is refreshed in place on every release, so the button always gives you the current plugin — and the download is pinned to the tracker host your project actually posts to. **2. Upload it to wp-content/mu-plugins/** Create `wp-content/mu-plugins/` if it doesn't already exist, then copy `searchable-ai-traffic.php` **directly** into it. > [!WARNING] > > WordPress does not load must-use plugins from subdirectories. The file has to sit at `wp-content/mu-plugins/searchable-ai-traffic.php`, not `wp-content/mu-plugins/searchable/searchable-ai-traffic.php`. There is nothing to activate. Must-use plugins load on every request automatically, and appear under the **Must-Use** filter on the admin Plugins screen (not in the main list, and with no activate/deactivate controls). ## Step 3: Add your credentials Open `wp-config.php` and paste the snippet from Step 1 **above** the line that reads `/* That's all, stop editing! Happy publishing. */`. ```php wp-config.php theme={null} define('SEARCHABLE_API_KEY', 'sk_live_YOUR_API_KEY'); define('SEARCHABLE_SITE_TOKEN', 'st_YOUR_SITE_TOKEN'); ``` Replace `sk_live_YOUR_API_KEY` with the key you generated. The site token is already filled in for you in the in-app snippet. > [!WARNING] > > The plugin deliberately stays inert — it sends nothing at all — while either constant still > contains `YOUR_`. If nothing arrives in your dashboard, this is the first thing to check. > [!NOTE] > > **Projects on a dedicated tracker hostname** see two extra `define()` lines in the in-app snippet > (`SEARCHABLE_API_URL` and `SEARCHABLE_BOTS_URL`). Don't drop them — without them the plugin falls > back to the shared host, and you'd end up downloading the plugin from one endpoint while posting > events to another. Most projects use the shared host and won't see these lines. ### Why `wp-config.php` and not the plugin file `wp-config.php` sits outside the plugin directory, is excluded from most deployment pipelines by default, and won't leak the key if `mu-plugins/` is ever served as plain text. ### Optional constants | Constant | Default | What it does | | - | - | - | | `SEARCHABLE_SEND_IP` | `true` | Set to `false` to omit the (already anonymized) IP entirely. Geo enrichment then falls back to edge metadata. | | `SEARCHABLE_API_URL` | `https://tracker.searchableanalytics.com/v1/events` | Ingest endpoint. Set automatically by the in-app snippet for projects on a dedicated tracker hostname. | | `SEARCHABLE_BOTS_URL` | `https://tracker.searchableanalytics.com/v1/bots.json` | Canonical bot list. Same rule as above. | ## Step 4: Verify Send a request to your site with a known crawler user agent: ```bash theme={null} curl -A "GPTBot/1.0" https://yourdomain.com/ ``` **Expected result:** the request appears in LLM Analytics within roughly 30 seconds. 1. Check the **Logs** tab first — it shows individual bot requests as they land. 2. On the **Setup** tab, the WordPress card flips from **Waiting for the first event from your WordPress site…** to **Connected — the plugin is sending events.** > [!TIP] > > Real crawler traffic follows on its own schedule. AI bots typically hit any indexed site within a > few hours, so don't read an empty dashboard an hour after install as a failure — the `curl` above > is the definitive test that the wiring works. ## How it works * **Bot list.** The plugin fetches Searchable's canonical bot list from `/v1/bots.json`, validates each pattern, and caches the compiled list in a transient for one hour. If the fetch fails it falls back to the last good list persisted in `wp_options`, so a network blip doesn't silently disarm the filter. Failures are cached for five minutes, so an outage never adds a fetch attempt to every request. * **Matching.** On the `shutdown` hook the plugin matches the request's user agent against that list. Non-matching requests do nothing — no outbound call, no work beyond the regex pass. WP-CLI, cron, and CLI requests are skipped entirely. * **Latency.** On PHP-FPM the response is flushed to the crawler via `fastcgi_finish_request()` *before* the outbound POST, so forwarding adds no latency. On other SAPIs the POST is blocking with a 2-second timeout, and only ever fires for matched crawler requests. * **IP handling.** The client IP is resolved proxy-aware (`CF-Connecting-IP` → left-most `X-Forwarded-For` → `X-Real-IP` → `REMOTE_ADDR`, each validated) and **anonymized on your server before sending** — the last IPv4 octet or IPv6 group is zeroed, so the full IP never leaves the site. ## What gets sent Only requests from user agents matching Searchable's canonical AI-crawler list are forwarded. Human traffic is never sent. Each matched request sends: * HTTP method, path, and full URL (query strings are stripped server-side before storage) * Response status code and server response time * User agent and referrer * An **anonymized** client IP — last octet zeroed, or omitted entirely with `SEARCHABLE_SEND_IP` * A `source` tag of `wordpress`, so these events are distinguishable from edge-connector or REST API events Cookies, request and response bodies, other headers, and full IP addresses are never sent. ## Optional: enhance the data The plugin is a **crawler-log source**. It powers the Overview, Pages, AI Crawlers and Logs tabs. To populate the **Visitors** tab — humans arriving on your site from AI answers — connect [Google Analytics 4](/integrations/google-analytics-4), [Search Console](/integrations/google-search-console), or Bing in step 3 of the Setup wizard. These are additive; they don't replace the crawler-log source. ## Troubleshooting > **Nothing appears in the dashboard** > > Work through these in order: > > 1. **Placeholders still in place.** The plugin no-ops while `SEARCHABLE_API_KEY` or `SEARCHABLE_SITE_TOKEN` contains `YOUR_`. Confirm both were replaced with real values. > 2. **Wrong directory.** The file must be directly in `wp-content/mu-plugins/` — not `plugins/`, and not a subdirectory. Confirm it appears under the **Must-Use** filter on the admin Plugins screen. If that filter isn't shown at all, WordPress isn't seeing any mu-plugin — check the path over SFTP. > 3. **Constant names.** They're case-sensitive and must be spelled exactly as in the snippet. > 4. **Outbound HTTP blocked.** Some managed hosts restrict `wp_remote_post` to an allowlist. Ask your host to allow `tracker.searchableanalytics.com` (or your dedicated tracker hostname). > **A page cache sits in front of WordPress** > > Full-page caches — WP Rocket, WP Super Cache, W3 Total Cache, Varnish, Cloudflare APO, or host-level caches on WP Engine and Kinsta — serve cached HTML without booting PHP. When that happens the `shutdown` hook never fires and the crawler visit is never recorded. > > Two options: > > * **Exclude known crawler user agents from the cache**, so their requests always reach PHP. > * **Move to an edge-level integration** — the [Cloudflare Worker](/setup/cloudflare-worker) is the usual answer, since it runs in front of the cache. > > If you do neither, you'll still capture the origin-level hits that pass through on cache misses and TTL expiry — an undercount, not a zero. > **The crawler views are empty but Visitors has data** > > You've connected Google Analytics or Search Console but no crawler-log source. The Overview, > Pages, AI Crawlers and Logs tabs need this plugin (or a Cloudflare, Vercel, or other edge source) > working. > **Only some crawlers are captured** > > Searchable's canonical bot list is regenerated **once daily** at 03:00 UTC, and the plugin caches its own copy for up to another hour. A newly added crawler can therefore take up to \~25 hours to start being captured. > > Deleting the `ai_traffic_bot_patterns` transient forces a refetch, but that only helps once the daily upstream publish has picked the new bot up. To see what the plugin is working from, fetch the list yourself: > > ```bash theme={null} > curl https://tracker.searchableanalytics.com/v1/bots.json > ``` > **Requests are slower after installing** > > On PHP-FPM they shouldn't be — the response is flushed before the POST. If your host runs mod\_php or another non-FPM SAPI, matched crawler requests carry a blocking POST bounded at 2 seconds. Human traffic is unaffected either way, since non-matching requests never make an outbound call. ## Next steps > **[See the data](/using-searchable/visibility-tracking)** > > Open LLM Analytics to see which assistants are crawling your site. > **[Connect Search Console](/integrations/google-search-console)** > > Layer in keyword data so you can correlate AI crawls with search demand. # Use Cases Source: https://docs.searchable.com/use-cases Paste-ready prompts for the Searchable MCP — weekly AI visibility briefs, competitor watch, citation gap analysis, and more. Searchable's MCP server turns your AI visibility, competitive, and content data into paste-ready prompts for Claude, ChatGPT, Cursor, and any other MCP-compatible assistant. Every prompt below is copy-paste-ready into a connected assistant and names the underlying Searchable MCP tools it exercises (the **Uses** line), so you know what's actually being queried before you run it. Not connected yet? See the [MCP integration guide](/integrations/mcp) to add the Searchable server to your assistant in a few minutes. ## Reporting ### Weekly AI Visibility Brief **Category:** Reporting · **Uses:** `get_visibility` (incl. `group_by: "date"` for the trend), `get_share_of_voice`, `search_sources`, `get_sentiment`, `generate_report` A full week-in-review — score, trend, share of voice, top sources, and sentiment — synthesized into a short narrative and published as a shareable link, without leaving your assistant. ``` Using the Searchable MCP, pull my brand's AI visibility score and trend for the last 7 days, our share of voice against tracked competitors, our top cited sources, and our sentiment breakdown. Summarize it as a short weekly brief with one recommended focus for the week, then generate a shareable report and give me the link. ``` ### Kick Off a Technical + AEO Audit **Category:** Reporting · **Uses:** `trigger_audit`, `get_site_health` Start a technical + AEO audit on your tracked pages and pull the resulting scores and open issues in one pass. *`trigger_audit` requires a grant or key with the **write** scope.* ``` Using the Searchable MCP, start a technical and AEO audit for my project (confirm it so it actually runs), then summarize the current site health — scores by page and any critical or high-severity issues. ``` ## Analysis ### Find Uncited Topics on ChatGPT **Category:** Analysis · **Uses:** `search_sources`, `get_source_detail` Spot which sites ChatGPT leans on for your tracked topics — and which of those never mention your brand. ``` Using the Searchable MCP, show me the top source domains ChatGPT cites for my tracked topics, then check a few of the highest-citation ones to see which don't mention our brand at all. ``` ### Spot-Check a Prompt's AI Answers **Category:** Analysis · **Uses:** `get_visibility`, `get_prompt_answers` Go from "which prompt is weakest" to "what did the AI actually say" in two calls. ``` Using the Searchable MCP, find our lowest mention-rate prompt from the last 30 days and show me the actual AI answers for it — did we get mentioned, who else did, and what got cited. ``` ### Google Search Console Quick Wins **Category:** Analysis · **Uses:** `get_gsc_performance` Surface the queries closest to a click-through breakthrough — ranking well, barely getting clicked. ``` Using the Searchable MCP, pull our Google Search Console quick-win queries — ranking positions 4 through 15 with decent impressions but low click-through rate — for the last 30 days. ``` ## Monitoring ### Competitor Movement Briefing **Category:** Monitoring · **Uses:** `get_competitors`, `get_share_of_voice`, `get_sentiment`, `get_visibility` A recurring check on tracked competitors — share-of-voice and sentiment moves worth knowing about, not just a data dump. ``` Using the Searchable MCP, check our tracked competitors for meaningful share-of-voice or sentiment movement since yesterday and flag anything worth our attention. ``` ### Check Shopping Carousel Visibility **Category:** Monitoring · **Uses:** `get_shopping_visibility` A quick pulse check on whether your products are showing up in AI shopping answers at all. ``` Using the Searchable MCP, check whether our products are showing up in AI shopping and product-carousel answers this month, and tell me our activation rate and top-ranked products. ``` ### AI Referral Traffic Pulse **Category:** Monitoring · **Uses:** `get_ai_traffic` First-party crawler and referral traffic from AI platforms, at a glance. ``` Using the Searchable MCP, give me an overview of AI crawler and AI-referral traffic for the last 30 days — totals by platform and our top crawled pages. ``` ## Content ### Close Citation Gaps Against Competitors **Category:** Content · **Uses:** `get_competitors`, `get_share_of_voice`, `search_sources`, `get_source_detail`, `get_source_trends` Find the sites already citing your competitors in AI answers, confirm you're genuinely absent, and rank them into an outreach and content list. ``` Using the Searchable MCP, find the source domains that cite my competitors in AI answers but rarely or never cite us, and turn that into a prioritized outreach and content list. ``` ### Content Brief From Prompt Gaps **Category:** Content · **Uses:** `get_visibility`, `get_prompt_answers`, `get_query_fanout` Turn your zero-visibility prompts into ready-to-write content briefs, grounded in what AI models actually ask and who currently wins the citation. ``` Using the Searchable MCP, find the tracked prompts where our brand has close to 0% mention rate, show me which competitors get cited instead, and write a content brief for each gap covering the sub-topics AI models actually ask about. ``` *** *Four of these workflows (the Weekly AI Visibility Brief, Competitor Movement Briefing, Close Citation Gaps Against Competitors, and Content Brief From Prompt Gaps) are also packaged as reusable Agent Skills for MCP clients that support the Agent Skills format — check back soon as we publish them to the public Agent Skills directories.* # AI-Powered Content Generation Source: https://docs.searchable.com/using-searchable/content-generation Generate optimized content that ranks in both traditional search and AI responses ## Overview Searchable's AI content generator creates high-quality, SEO and AEO-optimized articles based on your target keywords and prompts. Generate content that not only ranks in Google but also gets cited by AI models like ChatGPT and Claude. > **Choose Content Type** > > Ranked List, Topic Guide, Comparison, How-to Guide, Alternatives Guide > **Add Context** > > Let the Agent know what kind of post you'd like to write > **Research Phase** > > AI analyzes current trends, competitor content, and search patterns > **Check and Edit Outline** > > AI creates the article outline for you to review and modify > **Content Generation** > > AI creates optimized content with proper citations and structure > **Review and Publish** > > Review the generated content and publish directly to your platform ## Content Generation Process **1. Choose Content Type** Select from: * Ranked List * Topic Guide * Comparison * How-to Guide * Alternatives Guide **2. Define Topic & Keywords** Input: * Primary keyword or topic * Target audience * Desired word count * Tone and style preferences * Additional context **3. Research Phase** Searchable's AI: * Analyzes top-ranking content * Reviews competitor approaches * Identifies content gaps * Finds citation-worthy sources * Determines optimal structure **4. Content Generation** AI creates: * Optimized headlines and subheadings * Well-structured body content * Proper citations and references * Internal linking suggestions * Meta titles and descriptions **5. Review & Refine** You can: * Edit generated content * Request revisions * Add images and links * Adjust tone or depth * Add brand-specific details * Verify factual accuracy **6. Optimize & Publish** Finally: * Run final SEO/AEO check * Add structured data * Publish to your CMS ## Content Types ### Blog Post ![Blog Post Generation](https://mintcdn.com/searchable/Zyms3YY9H2Yf2QTr/images/content-blog-placeholder.png?fit=max\&auto=format\&n=Zyms3YY9H2Yf2QTr\&q=85\&s=7268ef5afd7c3e8f0bfb2dd6b26d02c1) **Best for:** * Thought leadership * Industry insights * News and trends * Opinion pieces **Optimization includes:** * Keyword integration * Internal linking * Featured snippet targets * Citation-worthy facts ### Article **Best for:** * In-depth topic exploration * Long-form content * Research-backed pieces * Educational resources **Optimization includes:** * Comprehensive topic coverage * Authoritative sources * Structured sections * Expert insights * Schema markup ### Listicle **Best for:** * Top 10 lists * Curated recommendations * Quick tips * Resource roundups **Optimization includes:** * Numbered structure * Scannable format * Engaging headlines * Visual appeal * Featured snippet optimization ### How-To Guide **Best for:** * Step-by-step tutorials * Process documentation * Educational content * Setup instructions **Optimization includes:** * Clear sequential steps * HowTo schema markup * Visual aids suggestions * Time estimates * Difficulty indicators ### Newsletter **Best for:** * Regular updates * Subscriber engagement * Curated content * Company news **Optimization includes:** * Engaging subject lines * Scannable sections * Clear CTAs * Personalization elements * Mobile-friendly format ### Comparison **Best for:** * Product vs. product * Feature comparisons * Buying guides * Alternative recommendations **Optimization includes:** * Fair, balanced comparisons * Specification tables * Pros and cons lists * Price comparisons * Decision frameworks ### Review **Best for:** * Product evaluations * Service reviews * Tool assessments * Honest recommendations **Optimization includes:** * Rating systems * Review schema markup * Pros and cons sections * Use case scenarios * Expert verdict ## AI Content Features ### Citation Management All generated content includes: * Properly cited sources * Hyperlinked references * Author credentials where relevant * Publication dates * Fact-checking notes > [!TIP] > > Citations make content more trustworthy for both readers and AI models. ### Structured Data Integration Automatic schema markup for: * Article schema * FAQ schema * HowTo schema * Product schema * Review schema ### SEO Optimization Every piece includes: * Target keyword in title and H1 * Optimal keyword density (2-3%) * LSI keyword integration * Internal linking suggestions * Optimized meta descriptions * Image alt text suggestions ### AEO Optimization Content is optimized for AI citation: * Clear, direct answers * Comprehensive topic coverage * Logical information hierarchy * Entity-rich language * Definition boxes * Summary sections ## Content Workflows ### Individual Articles For one-off content needs: 1. Navigate to **Content** → **New Article** 2. Enter topic and parameters 3. Generate outline 4. Review and approve 5. Generate full article 6. Edit and refine 7. Publish ### Content Calendar Plan content strategically: * Schedule generation dates * Coordinate with marketing campaigns * Track content clusters * Manage team assignments * Monitor publishing pipeline > [!WARNING] > > Automated posting requires integration with your website content management platform (CMS). ## Content Quality Standards > [!TIP] > > Original content (not duplicated) > [!TIP] > > Fact-checked and cited > [!TIP] > > Proper grammar and readability > [!TIP] > > Optimized for target keywords > [!TIP] > > Includes multimedia suggestions > [!TIP] > > Mobile-friendly formatting > [!TIP] > > Passes plagiarism checks > [!WARNING] > > Always review AI-generated content for accuracy, brand voice, and factual correctness before publishing. ## Performance Tracking Monitor how your content performs: ### SEO Metrics * Keyword rankings * Organic traffic * Backlinks acquired * Time on page * Bounce rate ### AEO Metrics * AI model citations * Prompt performance * Brand mentions * Share of voice ### Engagement Metrics * Page views * Social shares * Comments * Conversions > [!TIP] > > Searchable provides you with visibility on majority of key metrics, but it's always good to check your analytics platforms to validate your insights. ## Best Practices > [!TIP] > > Start with a clear content brief > [!TIP] > > Review and edit all AI-generated content > [!TIP] > > Add unique insights and examples > [!TIP] > > Include original data or research > [!TIP] > > Update content regularly (quarterly) > [!TIP] > > Monitor performance and optimize > [!TIP] > > Build content clusters around topics ## Content Templates Pre-built templates for common formats: * **Ultimate Guide Template**: Comprehensive topic coverage * **Comparison Template**: Product vs. product * **How-To Template**: Step-by-step instructions * **Listicle Template**: "Top 10" style articles * **News Analysis Template**: Industry news commentary * **Case Study Template**: Customer success stories ## Advanced Features ### Content Refresh Update existing content: * Identify outdated articles * Generate updated sections * Preserve top-performing elements * Add new information * Update statistics and examples ### Multi-Language Support (Custom plan) Generate content in multiple languages: * 20+ supported languages * Native speaker quality * Cultural adaptation * Local SEO optimization ### Custom Style Guides (Custom plan) Train AI on your brand voice: * Upload style guide documents * Provide example articles * Define tone preferences * Set terminology rules ## Limitations & Considerations **What AI Content Does Well:** * Research and information gathering * Structure and organization * SEO optimization * Citation finding * First drafts **What Requires Human Touch:** * Brand voice nuances * Original insights * Personal anecdotes * Controversial opinions * Final fact-checking > [!NOTE] > > AI content generation is a tool to enhance productivity, not replace human creativity and expertise. ## Pricing & Limits > **[View Content & Publishing Limits](/getting-started/plans-pricing#plans)** > > See detailed pricing and content generation limits for all plans ## Next Steps > **[Generate Your First Article](https://app.searchable.com/create/articles)** > > Start creating content now > **[Visibility Insights](/using-searchable/visibility-tracking)** > > Plan content based on AI demand > **[CMS Integrations](/integrations/overview)** > > Connect your publishing platform > **[Understanding Scores](/using-searchable/understanding-scores)** > > Optimize your content scores # Site Audits Source: https://docs.searchable.com/using-searchable/site-audits Run comprehensive technical and content audits to identify and fix optimization issues ## Overview Searchable's audit system performs deep analysis of your website to identify technical SEO issues, content quality problems, and AEO optimization opportunities. Get actionable recommendations prioritized by impact. > **Technical Audit** > > Performance, speed, and technical SEO > **Content Audit** > > Content quality and optimization > **AEO Audit** > > AI readiness and structured data ## Audit Types ### Quick Audit (30 seconds) Fast scan of critical issues: * Core Web Vitals check * Major technical errors * Critical content gaps * Priority recommendations **Best for:** * Quick health checks * Pre-launch reviews * Post-update validation ### Comprehensive Audit (2-10 minutes) Deep analysis of all pages: * Full technical SEO audit * Content quality analysis * AEO optimization review * Competitive benchmarking * Historical comparison **Best for:** * Monthly reviews * Strategy planning * Identifying opportunities ### Continuous Monitoring Automated ongoing audits: * Catch issues immediately * Track improvement trends * Alert on regressions * Flexible audit scheduling > [!NOTE] > > Number of pages monitored continuously varies depending on your plan. ## Running an Audit > **[Run Audit Now](https://app.searchable.com/on-page/site-health)** > > Start auditing your site **1. Navigate to Audits** Go to your project dashboard → **Run Audit** button **2. Select Audit Type** Choose quick or comprehensive based on your needs **3. Configure Scope** * All pages or specific URLs * Include/exclude sections * Mobile or desktop analysis **4. Start Audit** Click **"Start Audit"** and wait for completion. Most finish in 2-10 minutes. **5. Review Results** See prioritized issues and recommendations ## Understanding Audit Results ![Audit Results Dashboard](https://mintcdn.com/searchable/Zyms3YY9H2Yf2QTr/images/audit-results-placeholder.png?fit=max\&auto=format\&n=Zyms3YY9H2Yf2QTr\&q=85\&s=0ac0f3ef921efd2a85ff450a8d5e787d) ### Issue Severity Levels **Critical (Red)** * Severe problems impacting site function * Major SEO penalties * Broken core functionality * Security vulnerabilities **High (Orange)** * Significant impact on rankings * Poor user experience * Major performance issues * Missing critical content **Medium (Yellow)** * Moderate optimization opportunities * Minor technical issues * Content improvements needed * Best practice violations **Low (Blue)** * Nice-to-have optimizations * Minor improvements * Future-proofing recommendations * Advanced optimizations ## Common Issues Found ### Technical Issues > **Page Speed Problems** > > * Large image files > * Unoptimized JavaScript > * Render-blocking resources > * Slow server response > * Missing compression > > **Fix:** Compress images, minify code, enable caching > **Mobile Usability** > > * Text too small > * Touch targets too close > * Content wider than screen > * Incompatible plugins > > **Fix:** Implement responsive design, test on devices > **Crawl Errors** > > * 404 pages > * Redirect chains > * Broken internal links > * Blocked resources > * Invalid XML sitemap > > **Fix:** Fix links, create redirects, update sitemap > **HTTPS Issues** > > * Mixed content warnings > * Invalid SSL certificate > * HTTP version still accessible > * Insecure resources > > **Fix:** Force HTTPS, update resources, renew certificates ### Content Issues > **Thin Content** > > * Pages with less than 300 words > * Duplicate content > * Low-value pages > * Missing key information > > **Fix:** Expand content, consolidate duplicates, add value > **Missing Meta Data** > > * No title tags > * Missing meta descriptions > * Duplicate titles/descriptions > * Too long or too short > > **Fix:** Add unique, optimized meta data to each page > **Poor Content Structure** > > * Missing H1 tags > * Incorrect heading hierarchy > * No internal links > * Long paragraphs > > **Fix:** Implement proper heading structure, add links > **Keyword Issues** > > * No target keywords > * Keyword cannibalization > * Over-optimization > * Irrelevant keywords > > **Fix:** Define clear keyword strategy, consolidate content ### AEO Issues > **Missing Structured Data** > > * No schema markup > * Incomplete schemas > * Invalid JSON-LD > * Missing entities > > **Fix:** Implement Article, FAQ, HowTo, and Organization schemas > **Poor Answer Format** > > * No direct answers > * Unclear explanations > * Missing definitions > * Lack of examples > > **Fix:** Add clear answers, definitions, and examples > **Low Citation Authority** > > * No author bios > * Missing sources > * Outdated content > * No expertise signals > > **Fix:** Add author credentials, cite sources, update content ## Issue Prioritization Searchable automatically prioritizes fixes by: 1. **Impact Score**: How much it affects your scores 2. **Effort Level**: Easy, medium, or hard to fix 3. **Page Importance**: Based on traffic and conversions 4. **Issue Frequency**: How many pages are affected ### Quick Wins Filter for high-impact, low-effort fixes: * Missing alt text * Broken internal links * Missing meta descriptions * Duplicate content * Image optimization **Expected improvement:** 5-15 points in affected scores ### Long-Term Projects High-impact but time-intensive: * Site-wide redesign * Content rewrite projects * Technical infrastructure changes * Structured data implementation **Expected improvement:** 20-40 points over 3-6 months ## Taking Action on Issues ![Issue Detail View](https://mintcdn.com/searchable/Zyms3YY9H2Yf2QTr/images/issue-details-placeholder.png?fit=max\&auto=format\&n=Zyms3YY9H2Yf2QTr\&q=85\&s=56f6e4ca83b956aef5990b459c89152b) ### For Each Issue **View Details:** * Issue description * Why it matters * Affected pages * How to fix * Code examples * Expected impact **Actions:** * Mark as "In Progress" * Assign to team member * Add notes * Set deadline * Mark as "Resolved" ### Bulk Actions For issues affecting multiple pages: * Export affected URLs * Apply fixes in bulk * Track progress * Re-audit to verify ## Re-Auditing After Fixes **1. Fix Issues** Implement recommendations from audit **2. Wait for Changes** Allow 24-48 hours for changes to propagate **3. Run New Audit** Compare before and after results **4. Verify Improvements** Check that scores increased and issues resolved ## Best Practices > [!TIP] > > Run audits after any major site changes > [!TIP] > > Fix critical issues immediately > [!TIP] > > Tackle high-impact quick wins first > [!TIP] > > Set aside time monthly for audits > [!TIP] > > Track progress with historical comparison > [!TIP] > > Re-audit after implementing fixes > [!TIP] > > Monitor automated audits for regressions ## Integration Benefits ### With Google Search Console * Correlate audit findings with search data * Prioritize based on traffic impact * Track keyword-specific improvements ### With Google Analytics * Identify high-traffic pages needing fixes * Measure conversion impact * Prioritize by business value ### With CMS * Direct fix implementation * Content update workflows * Publishing optimization ## Common Questions > **How long does an audit take?** > > * Quick audit: 30-60 seconds > * Comprehensive audit: 2-10 minutes > * Time varies based on site size and complexity > **Will audits slow down my site?** > > No. Searchable's crawler is rate-limited and respects your server resources. We won't impact your site's performance for real users. > **Can I audit sites I don't own?** > > You can audit any public website, but detailed tracking features require domain verification on Professional, Agency, or Custom plans. > **How often should I run audits?** > > * After site changes: Immediately > * Regular monitoring: Weekly (Starter) or Daily (Professional+) > * Strategic reviews: Monthly comprehensive audits > **Can I export audits as a report?** > > This feature is coming soon. If you'd like to know more, reach out to us on [Community Slack](https://join.slack.com/t/searchablecommunity/shared_invite/zt-3d72toig3-5CNPzd1ujFQu_IanWGTHDA). ## Next Steps > **[Run Your First Audit](https://app.searchable.com/audits/new)** > > Start auditing your site now > **[Understanding Scores](/using-searchable/understanding-scores)** > > Learn what affects your scores > **[Troubleshooting](/using-searchable/troubleshooting)** > > Detailed fix instructions > **[Troubleshooting](/using-searchable/troubleshooting)** > > Common audit problems # Troubleshooting Source: https://docs.searchable.com/using-searchable/troubleshooting Solutions to common issues and problems in Searchable ## Common Issues Quick solutions to the most frequent problems users encounter. > **Zero visibility scores / No brand mentions** > > ### Problem > > Your prompts show 0% mention rate and AI models never cite your brand. > > ### Causes > > * Brand is too new or unknown > * No comprehensive content about your products/services > * Missing structured data and schema markup > * Low domain authority > > ### Solutions > > **1. Create Foundational Content** > > Write comprehensive articles about: > > * What your product/service is > * How it works > * Key features and benefits > * Use cases and examples > > **2. Implement Structured Data** > > Add schema markup: > > * Organization schema with logo and description > * Product/Service schemas > * FAQ schema > * Article schema on blog posts > > **3. Build Citation Authority** > > * Get mentioned in industry publications > * Earn backlinks from authoritative sites > * Create original research and data > * Establish thought leadership > > **4. Start with Easier Prompts** > > Begin with brand-specific prompts like: > > * "What is \[Your Brand]?" > * "Tell me about \[Your Company]" > > Then expand to category queries. > > **5. Be Patient** > > AI model training updates take time. Allow 4-8 weeks for improvements to show in visibility tracking. > > > [!WARNING] > > > > If you have zero mentions after 3 months of optimization, contact support for a strategy review. > **Audit not starting or timing out** > > ### Problem > > Audits get stuck or time out without completing. > > ### Causes > > * Large site with many pages > * Slow server response times > * Robots.txt blocking Searchable's crawler > * Firewall or security blocking our IP addresses > > ### Solutions > > **1. Check Robots.txt** > > Ensure Searchable's user agent is allowed: > > ```text theme={null} > User-agent: SearchableBot > Allow: / > ``` > > **2. Verify Server Performance** > > * Check your server response times > * Ensure your site isn't down or overloaded > * Contact your hosting provider if needed > > **3. Reduce Scope** > > * Limit audit to fewer pages > * Start with your most important pages > * Gradually expand scope > > **4. Whitelist Searchable IPs** > > If you have firewall rules, whitelist our crawler IPs (available in Settings → Security) > > **5. Contact Support** > > If issues persist after these steps, contact support with: > > * Your project ID > * The timestamp of failed audits > * Any error messages > **Integration connection failures** > > ### Problem > > Unable to connect Google Search Console, Google Analytics, or CMS integrations. > > ### Solutions by Integration > > **Google Search Console:** > > * Ensure you have Owner or Full User permission in GSC > * Verify the property is verified in GSC > * Use the same Google account for both GSC and Searchable > * Try disconnecting and reconnecting > * Clear browser cache and cookies > > **Google Analytics 4:** > > * Confirm you have at least Editor access to the GA4 property > > * Ensure GA4 property is active (not archived) > > * Check that data is flowing into GA4 > > * Verify you're selecting the correct property/stream > > > [!TIP] > > > > Most integration issues are resolved by disconnecting, clearing cache, and reconnecting with fresh credentials. > **Scores dropped suddenly** > > ### Problem > > Your scores decreased significantly without any changes on your end. > > ### Possible Causes > > * Competitors improved (relative scoring) > * New technical issues detected > * Third-party scripts causing slowdowns > * Server performance degradation > * Algorithm updates in scoring model > > ### Investigation Steps > > **1. Check Recent Changes** > > Review any recent updates to: > > * Your website > * Hosting environment > * Third-party integrations > * Content changes > > **2. Review New Issues** > > Go to Issues tab and look for: > > * New critical or high-priority issues > * Recently detected problems > * Issues affecting multiple pages > > **3. Compare Audits** > > Use audit history to compare: > > * Before and after scores > * New issues that appeared > * Changes in specific metrics > > **4. Check Server Performance** > > * Run PageSpeed Insights > * Check Core Web Vitals > * Test server response time > * Monitor uptime > > **5. Review Competitors** > > If you track competitors, check if they improved significantly > **Pages not being discovered** > > ### Problem > > Some of your pages aren't showing up in the monitored pages list. > > ### Causes > > * Pages not linked from homepage > * Blocked by robots.txt or noindex > * Sitemap is not available or not up to date > * Behind authentication > * JavaScript-rendered content > * Crawl depth limit reached > > ### Solutions > > **1. Check Internal Linking** > > Ensure pages are linked from your homepage or sitemap within 3 clicks > > **2. Verify Indexability** > > Check that pages: > > * Don't have noindex tags > * Aren't blocked in robots.txt > * Return 200 status codes > * Don't require login > > **3. Add Pages Manually** > > Go to Pages → Add Page and enter specific URLs you want to monitor > > **4. Adjust Crawl Settings** > > In Settings → Crawl Configuration: > > * Increase crawl depth > * Adjust included/excluded patterns > * Enable JavaScript rendering if needed > > **5. Submit Sitemap** > > * Ensure you have an XML sitemap > * Submit it in project settings > * Verify sitemap is accessible > **Slow dashboard loading** > > ### Problem > > Dashboard takes a long time to load or feels sluggish. > > ### Quick Fixes > > * **Reduce date range**: Shorter time periods load faster > * **Limit pages**: Monitor fewer pages > * **Clear filters**: Reset any active filters > * **Clear browser cache**: Force refresh (Ctrl+F5 or Cmd+Shift+R) > * **Use modern browser**: Chrome, Firefox, or Edge latest versions > * **Check internet connection**: Ensure stable, fast connection > > ### If Problems Persist > > * Try incognito/private mode > * Disable browser extensions > * Check browser console for errors (F12) > * Contact support with browser and network details > **API errors or rate limits** > > ### Problem > > Getting 429 (Too Many Requests) or other API errors. > > ### Solutions > > **Plan availability:** API keys are included with Custom contracts. Starter, Professional, and Agency accounts rely on in-platform workflows and cannot generate API keys. > > **If you have Custom access:** > > 1. Go to Settings → API to confirm credentials and observe usage > 2. Review which endpoints are consuming quota and adjust batching or caching > 3. Reach out to your success manager for temporary limit increases when needed > > **Reduce Usage:** > > * Implement caching on your end > * Batch requests where possible > * Optimize polling frequency > > **API Error Codes:** > > * 401: Invalid or expired API key > * 403: Insufficient permissions > * 404: Resource not found > * 429: Rate limit exceeded > * 500: Server error (contact support) > > ```bash Example: Check API Status theme={null} > curl -H "Authorization: Bearer YOUR_API_KEY" \ > https://api.searchable.com/v1/status > ``` > **Billing and subscription issues** > > ### Problem > > Payment failed, subscription cancelled, or billing questions. > > ### Common Issues > > **Payment Failed:** > > * Update payment method in Settings → Billing > * Check card hasn't expired > * Verify sufficient funds > * Contact bank if card is being declined > > **Subscription Not Active:** > > * Check email for failed payment notices > * Verify billing cycle and renewal date > * Contact support if charged but not upgraded > > **Upgrade Not Applied:** > > * Upgrades are immediate > * Try logging out and back in > * Clear browser cache > * Check confirmation email > > **Downgrade Questions:** > > * Downgrades apply at end of billing period > * Data preserved for 30 days > * Can re-upgrade anytime > > **Refunds:** > > * Contact support within 14 days > * Must not have exceeded usage limits > * One-time courtesy refunds available > **Team member access problems** > > ### Problem > > Team members can't access project or see incorrect data. > > ### Solutions > > **Member Can't See Project:** > > * Verify invitation was sent and accepted > * Check email spam folder for invitation > * Resend invitation from Settings → Team > * Ensure correct email address was used > > **Wrong Permissions:** > > * Review role assignments (Admin, Editor, Viewer) > * Update roles in Settings → Team → Manage > * Remember: changes take effect immediately > > **SSO Issues (Custom):** > > * Verify SSO is configured correctly > > * Check user is in correct organization > > * Test with different user > > * Review audit logs > > > [!NOTE] > > > > Team features are available on Professional, Agency, and Custom plans only. ## Getting Additional Help ### Support Resources > **[Documentation](/introduction)** > > Browse all documentation > **[Email Support](mailto:support@searchable.com)** > > [support@searchable.com](mailto:support@searchable.com) > **[Join Our Slack](https://join.slack.com/t/searchablecommunity/shared_invite/zt-3d72toig3-5CNPzd1ujFQu_IanWGTHDA)** > > Ask the community > **[Status Page](https://status.searchable.com)** > > Check system status ### Response Times * **Starter**: Email support, 2-3 business days * **Professional**: Priority support, 24-hour response * **Agency**: Dedicated success manager with same-business-day replies * **Custom**: SLA-backed support with contracted response times ### When Contacting Support Include these details for faster resolution: > [!TIP] > > Project ID (found in Settings) > [!TIP] > > What you were trying to do > [!TIP] > > Steps to reproduce the problem > [!TIP] > > Screenshots or error messages > [!TIP] > > Browser and OS version > [!TIP] > > When the problem started ## Feature Requests Have an idea for improving Searchable? 1. **Check Existing Requests**: Visit our [Feature Request Board](https://features.searchable.com) 2. **Vote on Requests**: Upvote features you want 3. **Submit New Ideas**: Create new feature requests 4. **Track Progress**: See what's in development Popular requests get prioritized! ## Report a Bug Found a bug? 1. Go to Settings → Help → Report Bug 2. Describe the bug in detail 3. Include steps to reproduce 4. Attach screenshots if helpful 5. Note your environment (browser, OS, plan) We aim to fix critical bugs within 24-48 hours. ## Emergency Support For critical production issues affecting your business: **Custom Customers:** * Use the emergency line provided in your onboarding packet * Direct access to engineering with 1-hour SLA **Agency Customers:** * Mark support tickets as "Urgent" * Dedicated manager responds within the same business day **Professional Customers:** * Flag tickets as high priority for fastest routing * Expect responses within 24 hours on business days **Starter & Trial Users:** * Join our Slack community for the quickest peer assistance * Critical security issues are escalated immediately ## Next Steps Still having issues? > **[Contact Support](mailto:support@searchable.com)** > > Get personalized help > **[Join Our Slack](https://join.slack.com/t/searchablecommunity/shared_invite/zt-3d72toig3-5CNPzd1ujFQu_IanWGTHDA)** > > Ask the community # Understanding Scores Source: https://docs.searchable.com/using-searchable/understanding-scores Learn how Searchable calculates and interprets your optimization scores > [!TIP] > > Ask your Searchable Agent for help if you want to understand or dig deeper into any of your scores. ## Score Overview Searchable uses a comprehensive scoring system (0-100) to measure your site's optimization across multiple dimensions. Each score provides actionable insights into specific aspects of your online presence. > **Technical Score** > > Site performance, speed, and technical SEO > **Content Score** > > Content quality, structure, and optimization > **AEO Score** > > Answer Engine Optimization readiness > **Overall Health** > > Combined weighted score ## Technical Score (0-100) ### What It Measures Your Technical Score evaluates the technical foundation of your website that affects both traditional search engines and AI crawlers. ![Technical Score Components](https://mintcdn.com/searchable/Zyms3YY9H2Yf2QTr/images/technical-score-breakdown.png?fit=max\&auto=format\&n=Zyms3YY9H2Yf2QTr\&q=85\&s=6f203e041fc38c19d7e98a98216f098f) ### Components & Weighting **Page Speed (30%)** * First Contentful Paint (FCP) * Largest Contentful Paint (LCP) * Time to Interactive (TTI) * Total Blocking Time (TBT) **Mobile Optimization (25%)** * Mobile-friendliness * Responsive design * Touch target sizing * Viewport configuration **Core Web Vitals (20%)** * LCP (Largest Contentful Paint) * FID (First Input Delay) * CLS (Cumulative Layout Shift) **Technical SEO (15%)** * XML sitemap presence and validity * Robots.txt configuration * Canonical tags * HTTPS implementation * Structured data **Crawlability (10%)** * Internal linking structure * Broken links * Redirect chains * URL structure * Server response codes ### Score Interpretation | Score Range | Status | What It Means | | - | - | - | | 90-100 | Excellent | Technical foundation is solid. Minor optimizations only. | | 70-89 | Good | Generally well-optimized with some room for improvement. | | 50-69 | Fair | Several technical issues affecting performance and crawlability. | | 30-49 | Poor | Significant technical problems that need immediate attention. | | 0-29 | Critical | Major technical issues severely impacting site performance. | ### How to Improve > **Boost Page Speed** > > **Quick Wins:** > > * Compress and optimize images > * Enable browser caching > * Minify CSS, JavaScript, and HTML > * Use a CDN for static assets > * Reduce server response time > > **Advanced:** > > * Implement lazy loading for images > * Use modern image formats (WebP, AVIF) > * Defer non-critical JavaScript > * Optimize web fonts > **Improve Mobile Experience** > > **Quick Wins:** > > * Test on multiple mobile devices > * Ensure text is readable without zooming > * Make buttons and links touch-friendly (min 48px) > * Avoid intrusive popups > > **Advanced:** > > * Implement responsive images > * Optimize for different screen sizes > * Test on slow network conditions > **Fix Core Web Vitals** > > **For LCP (\< 2.5s):** > > * Optimize largest element loading > * Preload important resources > * Reduce server response time > > **For FID (\< 100ms):** > > * Minimize JavaScript execution > * Break up long tasks > * Use web workers for heavy computations > > **For CLS (\< 0.1):** > > * Set size attributes for images/videos > * Reserve space for ads > * Avoid inserting content above existing content > **Enhance Technical SEO** > > **Quick Wins:** > > * Create and submit XML sitemap > * Implement canonical tags > * Add structured data (Schema.org) > * Fix broken links > > **Advanced:** > > * Optimize crawl budget > * Implement hreflang for international sites > * Use semantic HTML5 elements > * Optimize URL structure > [!TIP] > > Use the Issues tab to see specific technical problems. Searchable prioritizes fixes by impact. ## Content Score (0-100) ### What It Measures Your Content Score evaluates the quality, structure, and optimization of your content for both readers and AI models. ### Components & Weighting **Content Quality (30%)** * Depth and comprehensiveness * Original, valuable information * Clear, well-written text * Proper grammar and spelling * Content-to-code ratio **Keyword Optimization (25%)** * Target keyword usage * Keyword density (not overstuffed) * LSI keywords and semantic relevance * Keyword placement (title, headings, first paragraph) **Content Structure (20%)** * Proper heading hierarchy (H1, H2, H3) * Paragraph length and readability * List and table usage * Content organization * Table of contents for long content **Internal Linking (15%)** * Internal links quantity * Relevant anchor text * Link depth from homepage * Topical clustering **Meta Data (10%)** * Title tag optimization * Meta description quality * Header tags (H1-H6) * Image alt text * Open Graph tags ### Score Interpretation | Score Range | Status | What It Means | | - | - | - | | 90-100 | Excellent | Content is comprehensive, well-optimized, and highly valuable. | | 70-89 | Good | Quality content with minor optimization opportunities. | | 50-69 | Fair | Content needs improvement in structure or depth. | | 30-49 | Poor | Thin or poorly optimized content requiring significant work. | | 0-29 | Critical | Very thin or missing content. Major rewrite needed. | ### How to Improve > **Enhance Content Quality** > > **Quick Wins:** > > * Add more detailed explanations > * Include examples and case studies > * Update outdated information > * Add visual elements (images, videos) > * Improve readability (shorter sentences, clear language) > > **Advanced:** > > * Conduct original research > * Interview industry experts > * Create comprehensive guides > * Add unique data and statistics > **Optimize Keywords** > > **Quick Wins:** > > * Include target keyword in title > * Use keyword in first 100 words > * Add keyword to at least one H2 > * Use natural variations throughout > > **Advanced:** > > * Research and use LSI keywords > * Target long-tail variations > * Analyze top-ranking content > * Avoid keyword stuffing (2-3% density max) > **Improve Structure** > > **Quick Wins:** > > * Use clear heading hierarchy > * Break long paragraphs (3-4 sentences max) > * Add bullet points and numbered lists > * Include a table of contents for long articles > > **Advanced:** > > * Create content clusters around topics > * Use jump links for navigation > * Implement FAQ sections > * Add summary boxes > **Strengthen Internal Linking** > > **Quick Wins:** > > * Link to relevant related content > * Use descriptive anchor text > * Add 3-5 internal links per page > * Fix broken internal links > > **Advanced:** > > * Create topic clusters with pillar pages > * Implement breadcrumbs > * Build a hub-and-spoke structure > * Use contextual linking ## AEO Score (0-100) ### What It Measures Answer Engine Optimization (AEO) Score evaluates how well your content is optimized for AI-powered search engines and language models like ChatGPT, Claude, and Perplexity. > [!NOTE] > > AEO is the evolution of SEO for the AI era. It focuses on making your content easily understood and cited by AI models. ### Components & Weighting **Structured Data (25%)** * Schema.org markup implementation * Rich snippets optimization * Knowledge graph eligibility * Entity markup **Answer Format (25%)** * Clear, direct answers to questions * Featured snippet optimization * FAQ sections * How-to formats **Citation Worthiness (20%)** * Authoritative content * Proper sourcing and citations * Expert author bios * Trustworthy references **Semantic Richness (20%)** * Entity recognition * Topic depth and breadth * Related concepts coverage * Context clarity **AI Accessibility (10%)** * Clean HTML structure * Logical content flow * No excessive advertising * Fast loading ### Score Interpretation | Score Range | Status | What It Means | | - | - | - | | 90-100 | Excellent | Highly optimized for AI engines. Likely to be cited frequently. | | 70-89 | Good | Well-formatted for AI with some optimization opportunities. | | 50-69 | Fair | Basic AI compatibility. Needs structured data and clearer answers. | | 30-49 | Poor | Difficult for AI to parse. Missing key AEO elements. | | 0-29 | Critical | Not AI-friendly. Major restructuring needed. | ### How to Improve > **Implement Structured Data** > > **Quick Wins:** > > * Add Organization schema > * Implement Article schema > * Add FAQ schema for Q\&A content > * Include Product schema for e-commerce > > **Advanced:** > > * Add HowTo schema for guides > * Implement Review schema > * Use Event schema for events > * Add LocalBusiness schema > > ```json Example: Article Schema theme={null} > { > "@context": "https://schema.org", > "@type": "Article", > "headline": "Your Article Title", > "author": { > "@type": "Person", > "name": "Author Name" > }, > "datePublished": "2025-01-15", > "image": "https://example.com/image.jpg", > "publisher": { > "@type": "Organization", > "name": "Your Company", > "logo": { > "@type": "ImageObject", > "url": "https://example.com/logo.png" > } > } > } > ``` > **Optimize for Answers** > > **Quick Wins:** > > * Start with clear, concise answers > * Use question-based headings > * Create FAQ sections > * Format lists and steps clearly > > **Advanced:** > > * Target featured snippet formats > * Create comprehensive Q\&A content > * Use definition lists for terminology > * Add summary boxes at the top > **Build Citation Authority** > > **Quick Wins:** > > * Add author bios with credentials > * Cite reputable sources > * Link to original research > * Update content regularly > > **Advanced:** > > * Publish original research > * Get cited by authoritative sites > * Build topic authority > * Demonstrate E-E-A-T (Experience, Expertise, Authoritativeness, Trust) > **Enhance Semantic Content** > > **Quick Wins:** > > * Cover related subtopics comprehensively > * Use entity-rich language > * Define technical terms > * Create glossaries > > **Advanced:** > > * Build comprehensive topic clusters > * Answer related questions > * Cover the full topic ecosystem > * Use natural language variations ## Overall Health Score ### Calculation Your Overall Health Score is a weighted average of all component scores: ``` Overall Health = (Technical × 0.30) + (Content × 0.35) + (AEO × 0.35) ``` ### Why These Weights? * **Technical (30%)**: Foundation must be solid, but not everything * **Content (35%)**: Slightly higher weight as it's what users and AI consume * **AEO (35%)**: Critical for AI visibility in the modern search landscape ### Tracking Progress **View historical trends:** * Track scores over time (all plans) * Data retention: Starter (3 months), Professional (12 months), Agency & Custom (unlimited) * Compare time periods * Track after making changes * Export historical data **Best Practices:** * Check scores after each site update * Monitor trends over weeks, not days * Focus on improving weakest score first * Aim for balanced improvement across all scores ## Score Factors Not Included Searchable scores focus on controllable, measurable factors. We intentionally exclude: * Domain age and history * Raw traffic numbers * Social media engagement * Backlink quantity (only quality and relevance) These factors matter for SEO but are either uncontrollable or beyond the scope of content optimization. ## Frequently Asked Questions > **How often do scores update?** > > Scores update after each audit completes. All plans include continuous monitoring: > > * **Starter**: Scores update as audits run within your monthly allocation (25 pages) > * **Professional**: Higher audit capacity (100 pages) for more frequent updates > * **Agency**: 500-page allocation with dedicated success monitoring > * **Custom**: Tailored allocations with SLA-backed updates > > Set audit frequency in your project settings based on your needs and plan limits. > **Why did my score drop without changes?** > > Possible reasons: > > * Competitor improvements (relative scoring) > * New technical issues discovered > * Algorithm updates in our scoring model > * Server performance degradation > * Third-party script issues > * New pages with poor performance are added to the website > **Can I improve all scores simultaneously?** > > Yes! Many improvements help multiple scores: > > * Adding structured data helps both Content and AEO > * Improving page speed helps Technical and user experience > * Better content helps Content, AEO, and engagement > > Focus on issues that impact multiple scores first. > **What's a realistic score to target?** > > * **Technical**: 85+ is achievable for most sites > * **Content**: 80+ with quality content and optimization > * **AEO**: 75+ once structured data is implemented > * **Overall**: 80+ is excellent, 90+ is elite > > Focus on consistent improvement rather than perfection. > **Do scores directly correlate with AI visibility?** > > Higher scores generally mean better AI visibility, but it's not 1:1. Other factors include: > > * Brand recognition > * Content topic relevance > * Citation network > * User engagement > > Scores provide a strong foundation, but prompt strategy matters too. ## Next Steps > **[View Your Issues](/using-searchable/site-audits)** > > See specific problems affecting your scores > **[Working with Prompts](/using-searchable/working-with-prompts)** > > Improve AI visibility beyond technical scores > **[Run an Audit](https://app.searchable.com/on-page/site-health)** > > Generate fresh scores for your site > **[Integration Benefits](/integrations/overview)** > > Connect tools for deeper insights # AI Visibility Tracking Source: https://docs.searchable.com/using-searchable/visibility-tracking Monitor and analyze your brand mentions across AI platforms ## Overview Track how often and where your brand appears in AI model responses across 9 AI platforms. Visibility tracking helps you understand your AI presence and measure the impact of your content optimization efforts. > **Brand Mentions** > > Track frequency of brand citations > **Share of Voice** > > Your mentions vs. competitors > **Ranking Position** > > Where you appear in AI responses ## AI Platforms Monitored Searchable tracks brand visibility across **9 AI platforms**: * **ChatGPT** (OpenAI): Most widely used consumer AI * **Google AI Overviews**: AI-generated summaries in Google Search results * **Google AI Mode**: Google's AI-powered search experience * **Gemini**: Google's direct chat interface * **Perplexity**: AI-powered search and discovery * **Microsoft Copilot**: AI assistant across Windows, Edge, and Microsoft 365 * **Grok** (xAI): Real-time information access * **DeepSeek**: Advanced reasoning capabilities * **Claude** (Anthropic): Popular in enterprise — available on custom plans Platform availability varies by plan — see [Plans & Pricing](/getting-started/plans-pricing) for which platforms your tier includes. ## Key Metrics ### Visibility Score (0-100) Measures how prominently your brand appears when AI models mention you. **Calculation factors:** * Mention frequency * Position in responses * Context quality * Sentiment ### Presence Rate (0-100%) Percentage of tracked prompts that mention your brand at all. **Example:** * 50 prompts tracked * Brand mentioned in 15 responses * Presence Rate: 30% ### Share of Voice Your brand mentions as a percentage of total category mentions. **Competitive benchmarking:** * Compare against industry leaders * Track trends over time * Identify gaps and opportunities ## Visibility Reports ![Visibility Report Dashboard](https://mintcdn.com/searchable/Zyms3YY9H2Yf2QTr/images/visibility-report-placeholder.png?fit=max\&auto=format\&n=Zyms3YY9H2Yf2QTr\&q=85\&s=e1552c6776faff47bca47829520bb1a4) ### Daily Reports * Real-time mention tracking * Platform-by-platform breakdown * Prompt performance analysis * Trend indicators ### Weekly Summaries * Week-over-week changes * Top performing prompts * New citations discovered * Competitive insights ### Monthly Analysis * Long-term trends * Campaign impact measurement * Content correlation * Strategic recommendations ## Interpreting Your Results ### Strong Visibility (70-100) Your brand is frequently cited with good positioning. **Next steps:** * Maintain current content quality * Expand to adjacent topics * Track competitor movements ### Moderate Visibility (40-69) You're getting some mentions but inconsistently. **Next steps:** * Create more comprehensive content * Optimize for specific prompts * Improve citation authority ### Low Visibility (0-39) Your brand is rarely or never mentioned. **Next steps:** * Build foundational content * Focus on brand awareness * Create citation-worthy resources ## Competitor Tracking Compare your visibility against competitors: * Add competitors in Knowledge Base → Competitors * See side-by-side mention rates * Identify where competitors outperform * Discover gaps in your strategy * Find unoccupied niches you can own ## Source Domains Analysis ![Traffic Source Domains Breakdown](https://mintcdn.com/searchable/Zyms3YY9H2Yf2QTr/images/source-domains.png?fit=max\&auto=format\&n=Zyms3YY9H2Yf2QTr\&q=85\&s=abe40d452e792b29b7e15e6d0e0d7858) Track which domains drive AI visibility: * Continuous tracking on all plans * Historical data retention: Starter (3 months), Professional (12 months), Agency & Custom (unlimited) * Annotation for content updates * Export data for external analysis ## Platform-Specific Insights Different AI platforms have different behaviors: **ChatGPT:** * Most conservative with citations * Values authoritative sources * Updated frequently with new training **Claude:** * Detailed, thoughtful responses * Good at explaining reasoning * Values comprehensive content **Gemini:** * Strong Google integration * Favors recent content * Good for real-time queries **Perplexity:** * Always provides cited sources * Real-time web search integration * Conversational follow-up questions **Grok:** * Access to real-time data * More conversational tone * Good for current events ## Taking Action on Insights ### For Zero-Mention Prompts 1. Create comprehensive content targeting that prompt 2. Ensure proper structured data 3. Build citation authority 4. Wait 2-4 weeks for AI model updates ### For Low-Ranking Mentions 1. Improve content depth and quality 2. Add more specific examples 3. Include expert insights 4. Build authoritative backlinks ### For High Performers 1. Create similar content for related prompts 2. Expand on successful topics 3. Update regularly to maintain position ## Advanced Features ### Citation Analysis Understand not just mentions but context: * Sentiment of mentions (positive/neutral/negative) * Context in which you're cited * Specific features or benefits highlighted * Customer pain points addressed ### Prompt Performance Matrix Visualize all prompts at once: * X-axis: Mention rate * Y-axis: Average position * Size: Search volume estimate * Color: Category or priority ## Integration Benefits ### With Google Analytics * Correlate AI visibility with traffic * Measure AI-referred conversions * Identify high-value prompts ### With Google Search Console * Compare traditional SEO with AEO * Find synergies between channels * Unified keyword strategy ## Reporting & Exports **Available Reports:** * Visibility summary (PDF) * Prompt performance (CSV) * Competitive analysis (PDF) * Custom reports (Custom plan) **Scheduling:** * Daily email summaries * Weekly team reports * Monthly executive briefings ## Best Practices > [!TIP] > > Check visibility weekly, not daily > [!TIP] > > Focus on trends, not individual data points > [!TIP] > > Compare periods with similar content updates > [!TIP] > > Track competitors consistently > [!TIP] > > Correlate changes with content updates > [!TIP] > > Set realistic improvement targets (5-10% monthly) ## Common Questions > **How long before I see visibility changes?** > > * New content: 2-4 weeks for AI model updates > * Content improvements: 1-2 weeks > * Brand authority: 3-6 months > > AI models update regularly but not instantly. > **Why do results vary between platforms?** > > Each AI platform has: - Different training data cutoff dates - Unique algorithms and preferences - > Varying update frequencies - Different use cases and users > **Can I improve visibility without content?** > > Not significantly. AI visibility requires: > > * Quality content for models to reference > * Authoritative sources citing you > * Structured, accessible information > > Technical optimization helps, but content is king. ## Next Steps > **[Working with Prompts](/using-searchable/working-with-prompts)** > > Create better prompts for tracking > **[Content Generation](/using-searchable/content-generation)** > > Generate AI-optimized content > **[Understanding Scores](/using-searchable/understanding-scores)** > > Improve your technical foundation > **[Site Audits](/using-searchable/site-audits)** > > Run comprehensive site audits # Working with Prompts Source: https://docs.searchable.com/using-searchable/working-with-prompts Master the art of creating and managing prompts to track your AI visibility ## What Are Prompts? Prompts are questions or queries that you want to track across AI platforms. Think of them as the "keywords" of AI search - they help you understand when and how AI models mention your brand. While Search Engines like Google operate primarily on short phrases, or "keywords", AI search is much more conversational in nature. While you may search for 'best running sneakers' on Google, AI search will provide better answers for queries such as 'best road running sneakers on a budget, ideally waterproof'. ## Why Prompts Matter ### The AI Search Shift Users are increasingly asking AI assistants for recommendations: * "What's the best CRM for small businesses?" * "How do I optimize my website for SEO?" * "How can I introduce more colours to my decor?" If your brand isn't mentioned in these AI responses, you're invisible to potential customers. ### What We Track Searchable monitors your prompts across major AI platforms: > **OpenAI** > > ChatGPT > **Anthropic** > > Claude (Opus, Sonnet, Haiku) > **Google** > > Gemini (Pro, Ultra) > **Perplexity** > > Perplexity AI > **Coming Soon** > > xAI Grok, DeepSeek, Meta Llama ## Creating Effective Prompts ### Prompt Anatomy A great prompt has three components: 1. **Context**: Who is asking and what's their situation? 2. **Intent**: What information are they seeking? 3. **Specificity**: Enough detail to generate useful responses ### Good vs. Bad Prompts ```text Bad Prompt theme={null} CRM software ``` ```text Good Prompt theme={null} What are the best CRM software options for a small business with 10-20 employees that integrates with email marketing? ``` **Why the difference?** * Bad: Too vague, will generate generic lists * Good: Specific context, clear intent, qualified criteria > [!WARNING] > > Reusing your SEO keywords as prompts is not a good AEO strategy. ### Prompt Templates by Industry **SaaS** **Product Comparison** ```text theme={null} What are the best [your category] tools for [target audience] who need [key feature]? ``` **Problem-Solution** ```text theme={null} How can [target audience] solve [specific problem] without [common pain point]? ``` **Feature Inquiry** ```text theme={null} Which [product category] offers [specific feature] and integrates with [popular tool]? ``` **E-commerce** **Product Recommendations** ```text theme={null} What's the best [product type] for [use case] under [price range]? ``` **Buying Guides** ```text theme={null} What should I look for when buying [product category] for [specific need]? ``` **Alternative Seeking** ```text theme={null} What are good alternatives to [competitor] that are [better/cheaper/more feature-rich]? ``` **Services** **Service Provider Selection** ```text theme={null} How do I choose the right [service type] for [specific situation]? ``` **Local Queries** ```text theme={null} Who are the top [service providers] in [location] for [specific service]? ``` **Qualification Questions** ```text theme={null} What questions should I ask when hiring a [service professional]? ``` **Content/Media** **Resource Discovery** ```text theme={null} What are the best resources for learning [topic] at [skill level]? ``` **Content Recommendations** ```text theme={null} Can you recommend [content type] about [topic] that covers [specific aspect]? ``` **Comparison Content** ```text theme={null} What's the difference between [concept A] and [concept B] in [context]? ``` ## Prompt Strategy ### The Prompt Pyramid Build your prompt library strategically: **1. Foundation: Brand Queries (5-10 prompts)** Direct mentions of your brand name **Examples:** * "What is \[Your Brand]?" * "Tell me about \[Your Company]" * "How does \[Your Product] work?" **Purpose**: Establish baseline brand awareness **2. Middle: Category Queries (10-20 prompts)** Your product/service category without brand name **Examples:** * "What are the best \[category] for \[audience]?" * "How do I choose a \[category]?" * "What features should I look for in \[category]?" **Purpose**: Compete for category dominance **3. Top: Problem/Solution Queries (20-50 prompts)** Problems your product solves, without mentioning solutions **Examples:** * "How can I \[achieve goal]?" * "What's the best way to \[solve problem]?" * "I'm struggling with \[pain point], what should I do?" **Purpose**: Capture users before they know solutions exist **4. Peak: Adjacent Topics (10-30 prompts)** Related topics where your brand adds value **Examples:** * "What is \[related concept]?" * "How do \[adjacent process] work?" * "Best practices for \[related activity]?" **Purpose**: Build topic authority beyond your core offering ### Prompt Diversity Balance your prompts across dimensions: | Dimension | Why It Matters | Example Balance | | - | - | - | | **Specificity** | Broad prompts show awareness, specific ones show purchase intent | 40% broad, 60% specific | | **Buying Stage** | Track the full customer journey | 30% awareness, 40% consideration, 30% decision | | **Competitor Mentions** | Understand your competitive position | 20% include competitor names | | **Question Types** | Different formats appeal to different users | Mix "what", "how", "why", "which" | ## Managing Your Prompt Library ### In the Searchable Dashboard ![Prompts Dashboard](https://mintcdn.com/searchable/Zyms3YY9H2Yf2QTr/images/prompts-dashboard.png?fit=max\&auto=format\&n=Zyms3YY9H2Yf2QTr\&q=85\&s=9ccf5c84329c8508f7a1f38fe4033775) **Actions Available:** * Create new prompts * Edit existing prompts * Archive low-performing prompts * Bulk import/export * Analyze performance * View mention history ### Creating a Prompt **1. Navigate to Prompts** Go to **Prompts** tab in your project **2. Click Create Prompt** Click the **"+ New Prompt"** button **3. Enter Your Question** Type your prompt exactly as a user would ask it > [!TIP] > > Test your prompt in ChatGPT or another AI platform first to see what kinds of responses it generates. **4. Add Metadata (Optional)** * **Category**: Organize prompts by type * **Priority**: Mark important prompts * **Tags**: Add labels for filtering **5. Save and Run** Click **"Save & Run"** to immediately test across all AI platforms > [!NOTE] > > Initial results appear within 30-60 seconds. Historical tracking begins immediately. ### Bulk Import For adding many prompts at once: 1. **Download Template** * Go to Prompts → **Import** * Download the CSV template 2. **Fill Template** * One prompt per row * Include category and tags columns * Maximum 1000 prompts per import ```csv Example: prompts-import.csv theme={null} prompt,category,tags,priority "What are the best CRM tools for startups?",Product Comparison,"crm,startup,comparison",High "How do I automate my email marketing?",Problem Solution,"email,automation,how-to",Medium "What is [Your Brand]?",Brand Query,"brand,awareness",High ``` 3. **Upload & Review** * Upload your CSV file * Review detected prompts * Fix any errors * Click **"Import All"** ### Prompt Templates Save time with pre-built templates: **Access Templates:** * Go to Prompts → **Templates** * Browse by industry or use case * Click **"Use Template"** * Customize for your brand **Available Template Categories:** * SaaS Product Queries * E-commerce Product Recommendations * Service Provider Selection * Content Discovery * Local Business Queries * B2B Software Evaluation ## Analyzing Prompt Performance ### Key Metrics > **Mention Rate** > > How often your brand appears in responses > **Average Position** > > Where you rank when mentioned (1-10) > **Share of Voice** > > Your mentions vs. total category mentions > **Sentiment** > > Positive, neutral, or negative mentions ### Interpreting Results **High Performers (80%+ mention rate)** * Great! These prompts consistently mention your brand * Action: Create similar prompts to expand this success **Medium Performers (30-79% mention rate)** * Promising opportunities for improvement * Action: Optimize content to better match these prompts **Low Performers (below 30% mention rate)** * Either too competitive or need better content alignment * Action: Create targeted content or refine prompt wording **Zero Mentions** * Not necessarily bad - could be opportunity queries * Action: Create comprehensive content specifically for these prompts ### Prompt Optimization Cycle **1. Run for 2 Weeks** Let prompts collect data across multiple AI platforms **2. Analyze Patterns** Identify which prompts generate mentions and which don't **3. Create Content** Write articles targeting zero-mention prompts **4. Optimize Existing** Improve pages that get partial mentions to increase frequency **5. Add Variations** Create new prompts based on what's working **6. Repeat Monthly** Continuous optimization is key to AI visibility ## Advanced Prompt Techniques ### Competitor Tracking Include competitors in your prompts to understand your position: **Comparison Prompts:** ```text theme={null} What's better: [Your Brand] or [Competitor]? Compare [Your Brand] vs [Competitor] for [use case] [Your Brand] alternatives that are [better criterion] ``` **Market Landscape:** ```text theme={null} What are the top 5 [category] tools? Who are the leaders in [industry]? What are the best [category] companies? ``` ### Sentiment Analysis Track not just mentions, but how you're mentioned: * Monitor positive vs. negative sentiment * Identify common criticisms * Track feature-specific sentiment * Compare sentiment to competitors ### Time-Based Prompts Some prompts are seasonal or time-sensitive: **Examples:** ```text theme={null} Best [product] for 2025 Top [category] tools this year Latest trends in [industry] ``` **Strategy:** * Create year-specific prompts * Update annually * Archive outdated versions ### Local Prompts For location-based businesses: **Examples:** ```text theme={null} Best [service] in [city] Top [business type] near [location] Where to find [product] in [area] ``` ## Prompt Best Practices > [!TIP] > > Start with 10-20 prompts and expand gradually > [!TIP] > > Test prompts in ChatGPT before adding to Searchable > [!TIP] > > Focus on questions your customers actually ask > [!TIP] > > Include competitor names strategically > [!TIP] > > Update prompts quarterly to stay current > [!TIP] > > Archive low-performing prompts after 3 months > [!TIP] > > Create content specifically targeting zero-mention prompts > [!WARNING] > > Avoid "gaming" the system by creating prompts that are too specific to your brand. Focus on genuine user queries for meaningful insights. ## Industry-Specific Strategies ### SaaS * Focus heavily on feature comparison prompts * Track integration-related queries * Monitor pricing and plan comparisons * Include use-case specific prompts ### E-commerce * Product recommendation prompts * Price-point specific queries * Alternative and comparison prompts * Seasonal buying queries ### Services * Problem-solution prompts * Qualification and vetting questions * Local service queries * Industry-specific how-to prompts ### Content/Publishing * Resource discovery prompts * Learning and education queries * "Best of" list prompts * Tutorial and guide requests ## Frequently Asked Questions > **How many prompts should I have?** > > * Start with 10-20 prompts and expand based on performance > * Most businesses find 50-200 prompts sufficient for comprehensive coverage > * Quality matters more than quantity - focus on prompts your customers actually ask > **How often should I update my prompts?** > > * **Add new prompts**: Monthly > * **Review performance**: Weekly > * **Update existing prompts**: Quarterly > * **Archive low performers**: Every 3 months > > Prompt strategy should evolve with your business and market. > **Can prompts be too specific?** > > Yes! Overly specific prompts (e.g., "Tell me everything about \[Your Brand]'s exact feature set") won't reflect real user queries. Balance specificity with natural language. > **Why are my prompts showing zero mentions?** > > Common reasons: > > * Your brand isn't well-known enough yet > * No content specifically answers that prompt > * The prompt is too competitive > * AI models lack training data about your brand > > **Solution**: Create comprehensive content targeting those prompts and build brand authority. > **Should I include competitor names in prompts?** > > Yes, strategically! Including competitors helps you: > > * Understand your competitive position > * Identify where you're being compared > * Find alternative-seeking queries > > Aim for 20-30% of prompts to include competitor names. > **How do I track prompts in different languages?** > > Custom plans support multi-language prompts. Contact sales for: > > * Language-specific prompt libraries > * Region-specific AI model tracking > * International brand visibility monitoring ## Prompt Library Resources > **[Browse Templates](https://app.searchable.com/prompts/templates)** > > Industry-specific prompt templates > **[Explore Prompts](https://app.searchable.com/prompts/explore)** > > AI-powered prompt suggestions > **[Join Our Slack](https://join.slack.com/t/searchablecommunity/shared_invite/zt-3d72toig3-5CNPzd1ujFQu_IanWGTHDA)** > > Connect with other users and share prompts ## Next Steps > **[Run Your First Prompt](https://app.searchable.com/prompts)** > > Create your first prompt now > **[AI Visibility Tracking](/using-searchable/visibility-tracking)** > > Learn to interpret visibility reports > **[Content Generation](/using-searchable/content-generation)** > > Create content that gets AI citations > **[Advanced Prompt Techniques](/advanced/custom-prompts)** > > Master advanced prompt strategies