Tutorial

How to Build an Automated LLM Visibility Scanner

Scan how visible your brand is across LLM-powered search results. Track AI citations, organic presence, and AEO signals.

LLMs now power search experiences across Google AI Mode, Bing Copilot, and Perplexity. Your brand's visibility in these AI-generated responses directly impacts traffic. This scanner checks your brand presence across search results that feed LLM responses, tracking AI citations, featured snippets, and People Also Ask data. Each brand scan costs $0.025 across 5 keywords.

Prerequisites

  • Python 3.8+
  • requests library
  • A Scavio API key from scavio.dev
  • Target keywords and brand domain

Walkthrough

Step 1: Scan LLM visibility signals across keywords

Check multiple search features that LLMs use to generate responses.

Python
import os, requests, json
from datetime import datetime
from collections import defaultdict

API_KEY = os.environ['SCAVIO_API_KEY']
SH = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}

BRAND_DOMAIN = 'scavio.dev'
KEYWORDS = [
    'best search api for ai agents',
    'mcp search tool setup',
    'web search api comparison 2026',
    'serp api free tier options',
    'search api for rag pipeline',
]

def scan_llm_visibility(keyword, domain):
    data = requests.post('https://api.scavio.dev/api/v1/search',
        headers=SH, json={'query': keyword, 'country_code': 'us'}, timeout=10).json()
    domain_l = domain.lower()
    # AI Overview / Answer Box (feeds LLM responses)
    ai = data.get('ai_overview', data.get('answer_box', {}))
    ai_cited = domain_l in json.dumps(ai).lower() if ai else False
    # Featured Snippet (high-priority LLM source)
    featured = data.get('featured_snippet', {})
    in_featured = domain_l in json.dumps(featured).lower() if featured else False
    # People Also Ask (LLM follow-up context)
    paa = data.get('people_also_ask', [])
    in_paa = any(domain_l in json.dumps(q).lower() for q in paa)
    # Organic position (base visibility)
    organic = data.get('organic_results', [])
    org_pos = next((i+1 for i, r in enumerate(organic) if domain_l in r.get('link', '').lower()), None)
    # Top 3 domains (who LLMs are citing)
    top3 = [r.get('displayed_link', '').split('/')[0] for r in organic[:3]]
    return {
        'keyword': keyword,
        'ai_cited': ai_cited,
        'has_ai_overview': bool(ai),
        'in_featured': in_featured,
        'in_paa': in_paa,
        'organic_pos': org_pos,
        'top3_domains': top3,
    }

scans = []
for kw in KEYWORDS:
    scan = scan_llm_visibility(kw, BRAND_DOMAIN)
    scans.append(scan)
    signals = []
    if scan['ai_cited']: signals.append('AI')
    if scan['in_featured']: signals.append('FS')
    if scan['in_paa']: signals.append('PAA')
    pos = f'#{scan["organic_pos"]}' if scan['organic_pos'] else '-'
    print(f'  {kw[:40]:40} | {pos:5} | {", ".join(signals) or "none"}')
print(f'\nCost: ${len(KEYWORDS) * 0.005:.3f}')

Step 2: Calculate LLM visibility score

Aggregate scan data into a composite visibility score.

Python
def calculate_llm_score(scans, domain):
    total = len(scans)
    # Component scores
    ai_cited = sum(1 for s in scans if s['ai_cited'])
    has_ai = sum(1 for s in scans if s['has_ai_overview'])
    featured = sum(1 for s in scans if s['in_featured'])
    paa = sum(1 for s in scans if s['in_paa'])
    top3 = sum(1 for s in scans if s['organic_pos'] and s['organic_pos'] <= 3)
    top10 = sum(1 for s in scans if s['organic_pos'] and s['organic_pos'] <= 10)
    # Weighted score (100 max)
    ai_score = (ai_cited / max(has_ai, 1)) * 35  # AI citations worth most
    featured_score = (featured / total) * 25  # Featured snippets
    paa_score = (paa / total) * 15  # PAA presence
    organic_score = (top3 / total) * 15 + (top10 / total) * 10  # Organic ranking
    total_score = ai_score + featured_score + paa_score + organic_score
    print(f'\n=== LLM Visibility Score: {domain} ===')
    print(f'  Overall: {total_score:.0f}/100')
    print(f'\n  Component Breakdown:')
    print(f'    AI Citations:    {ai_score:.0f}/35 ({ai_cited}/{has_ai} keywords with AI)')
    print(f'    Featured Snippet: {featured_score:.0f}/25 ({featured}/{total} keywords)')
    print(f'    People Also Ask: {paa_score:.0f}/15 ({paa}/{total} keywords)')
    print(f'    Organic Top 3:   {top3}/{total} keywords')
    print(f'    Organic Top 10:  {top10}/{total} keywords')
    return {
        'total_score': total_score,
        'ai_score': ai_score,
        'featured_score': featured_score,
        'paa_score': paa_score,
        'organic_score': organic_score,
    }

visibility = calculate_llm_score(scans, BRAND_DOMAIN)

Step 3: Generate actionable recommendations

Identify gaps and provide specific actions to improve LLM visibility.

Python
def generate_recommendations(scans, visibility, domain):
    print(f'\n{"=" * 60}')
    print(f'  LLM VISIBILITY SCANNER REPORT')
    print(f'  Brand: {domain} | Date: {datetime.now().strftime("%Y-%m-%d")}')
    print(f'  Score: {visibility["total_score"]:.0f}/100')
    print(f'{"=" * 60}')
    recs = []
    # AI citation gaps
    ai_gaps = [s['keyword'] for s in scans if s['has_ai_overview'] and not s['ai_cited']]
    if ai_gaps:
        recs.append({
            'priority': 'HIGH',
            'action': 'Optimize for AI citations',
            'keywords': ai_gaps,
        })
    # Featured snippet opportunities
    fs_gaps = [s['keyword'] for s in scans if not s['in_featured'] and s['organic_pos'] and s['organic_pos'] <= 5]
    if fs_gaps:
        recs.append({
            'priority': 'MEDIUM',
            'action': 'Target featured snippets (already ranking top 5)',
            'keywords': fs_gaps,
        })
    # Not ranking at all
    absent = [s['keyword'] for s in scans if not s['organic_pos']]
    if absent:
        recs.append({
            'priority': 'HIGH',
            'action': 'Create content (not ranking)',
            'keywords': absent,
        })
    print(f'\n  Recommendations ({len(recs)}):')
    for r in recs:
        print(f'\n  [{r["priority"]:6}] {r["action"]}')
        for kw in r['keywords'][:3]:
            print(f'    - {kw}')
    # Competitor visibility
    print(f'\n  Top Competitors (by AI/organic presence):')
    all_domains = defaultdict(int)
    for s in scans:
        for d in s.get('top3_domains', []):
            if d and domain not in d:
                all_domains[d] += 1
    for d, count in sorted(all_domains.items(), key=lambda x: -x[1])[:5]:
        print(f'    {d:30} | Top 3 in {count}/{len(scans)} keywords')
    print(f'\n  Scan cost: ${len(scans) * 0.005:.3f}')
    print(f'  Monthly (daily): ${len(scans) * 0.005 * 30:.2f}')

generate_recommendations(scans, visibility, BRAND_DOMAIN)

Python Example

Python
import os, requests, json
SH = {'x-api-key': os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}

def llm_visibility(keyword, domain):
    data = requests.post('https://api.scavio.dev/api/v1/search',
        headers=SH, json={'query': keyword, 'country_code': 'us'}, timeout=10).json()
    ai = data.get('ai_overview', data.get('answer_box', {}))
    cited = domain.lower() in json.dumps(ai).lower() if ai else False
    pos = next((i+1 for i, r in enumerate(data.get('organic_results', [])) if domain in r.get('link', '')), None)
    print(f'{keyword[:35]:35} | AI: {cited} | Pos: {pos or "-"}')

llm_visibility('best search api', 'scavio.dev')

JavaScript Example

JavaScript
const SH = { 'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json' };
const data = await fetch('https://api.scavio.dev/api/v1/search', {
  method: 'POST', headers: SH,
  body: JSON.stringify({ query: 'best search api', country_code: 'us' })
}).then(r => r.json());
const ai = data.ai_overview || data.answer_box || {};
const cited = JSON.stringify(ai).toLowerCase().includes('scavio');
const pos = (data.organic_results || []).findIndex(r => r.link?.includes('scavio.dev'));
console.log(`AI cited: ${cited} | Position: ${pos >= 0 ? pos + 1 : 'absent'}`);

Expected Output

JSON
  best search api for ai agents            | #3    | AI
  mcp search tool setup                    | #2    | AI, FS
  web search api comparison 2026           | #5    | none
  serp api free tier options               | #4    | PAA
  search api for rag pipeline              | #6    | none

Cost: $0.025

=== LLM Visibility Score: scavio.dev ===
  Overall: 52/100

  Component Breakdown:
    AI Citations:    18/35 (2/4 keywords with AI)
    Featured Snippet: 5/25 (1/5 keywords)
    People Also Ask: 3/15 (1/5 keywords)
    Organic Top 3:   2/5 keywords

============================================================
  LLM VISIBILITY SCANNER REPORT
  Brand: scavio.dev | Date: 2026-05-21
  Score: 52/100
============================================================

  Recommendations (2):

  [HIGH  ] Optimize for AI citations
    - web search api comparison 2026
    - serp api free tier options

  Scan cost: $0.025
  Monthly (daily): $0.75

Related Tutorials

Frequently Asked Questions

Most developers complete this tutorial in 15 to 30 minutes. You will need a Scavio API key (free tier works) and a working Python or JavaScript environment.

Python 3.8+. requests library. A Scavio API key from scavio.dev. Target keywords and brand domain. A Scavio API key gives you 250 free credits per month.

Yes. The free tier includes 250 credits per month, which is more than enough to complete this tutorial and prototype a working solution.

Scavio has a native LangChain package (langchain-scavio), an MCP server, and a plain REST API that works with any HTTP client. This tutorial uses the raw REST API, but you can adapt to your framework of choice.

Start Building

Scan how visible your brand is across LLM-powered search results. Track AI citations, organic presence, and AEO signals.