ScavioScavio
ProductPricingDocs
Sign InGet Started
  1. Home
  2. Tutorials
  3. How to Build a Weekly SEO Report as a Claude Code Skill
Tutorial

How to Build a Weekly SEO Report as a Claude Code Skill

Build a /weekly-report Claude Code skill that pulls SERP data, tracks rankings, and formats a dashboard. Python + Scavio API.

Get Free API KeyAPI Docs

A Claude Code skill that runs /weekly-report pulls live SERP data for your target keywords, compares against last week, and formats a concise dashboard in your terminal. The skill runs as a single command, costs $0.005 per keyword checked, and produces a snapshot you can share with your team or paste into Slack.

Prerequisites

  • Claude Code CLI installed
  • Python 3.8+
  • A Scavio API key from scavio.dev
  • Target keywords to track

Walkthrough

Step 1: Design the weekly report data model

Define what data the report needs and how to fetch it.

Python
import os, requests, json, sqlite3
from datetime import datetime, timedelta

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

KEYWORDS = ['serp api', 'search api python', 'web scraping api',
            'ai agent search tool', 'mcp search server']
MY_DOMAIN = 'scavio.dev'

db = sqlite3.connect('weekly_report.db')
db.execute('''CREATE TABLE IF NOT EXISTS weekly (
    keyword TEXT, position INTEGER, has_ao INTEGER,
    brand_cited INTEGER, checked_at TEXT
)''')
db.commit()

def check_keyword(keyword):
    data = requests.post('https://api.scavio.dev/api/v1/search',
        headers=SH, json={'query': keyword, 'country_code': 'us',
                          'include_ai_overview': True}).json()
    organic = data.get('organic_results', [])
    pos = next((r['position'] for r in organic if MY_DOMAIN in r.get('link', '')), None)
    has_ao = bool(data.get('ai_overview'))
    ao_text = json.dumps(data.get('ai_overview', {})).lower()
    cited = MY_DOMAIN in ao_text if has_ao else False
    db.execute('INSERT INTO weekly VALUES (?,?,?,?,?)',
        (keyword, pos or 0, int(has_ao), int(cited), datetime.now().isoformat()))
    db.commit()
    return {'keyword': keyword, 'position': pos, 'has_ao': has_ao, 'cited': cited}

print('Weekly report data model ready.')

Step 2: Build the report generation logic

Create the weekly report that compares current vs previous data.

Python
def get_previous_week(keyword):
    week_ago = (datetime.now() - timedelta(days=7)).isoformat()
    row = db.execute(
        'SELECT position, has_ao, brand_cited FROM weekly WHERE keyword=? AND checked_at < ? ORDER BY checked_at DESC LIMIT 1',
        (keyword, week_ago)).fetchone()
    return {'position': row[0], 'has_ao': bool(row[1]), 'cited': bool(row[2])} if row else None

def weekly_report():
    results = []
    for kw in KEYWORDS:
        current = check_keyword(kw)
        previous = get_previous_week(kw)
        change = None
        if previous and current['position'] and previous['position']:
            change = previous['position'] - current['position']
        results.append({**current, 'change': change, 'previous': previous})
    return results

def format_report(results):
    now = datetime.now().strftime('%Y-%m-%d')
    lines = []
    lines.append(f'Weekly SEO Report - {now}')
    lines.append(f'Domain: {MY_DOMAIN}')
    lines.append(f'Keywords: {len(results)}')
    lines.append('')
    lines.append(f'{"Keyword":30} | {"Pos":>4} | {"Chg":>4} | {"AO":>3} | {"Cited":>5}')
    lines.append('-' * 60)
    for r in results:
        pos = f'#{r["position"]}' if r['position'] else '-'
        chg = f'{r["change"]:+d}' if r['change'] is not None else 'NEW'
        ao = 'Y' if r['has_ao'] else 'N'
        cited = 'Y' if r['cited'] else 'N'
        lines.append(f'{r["keyword"]:30} | {pos:>4} | {chg:>4} | {ao:>3} | {cited:>5}')
    return '\n'.join(lines)

results = weekly_report()
print(format_report(results))

Step 3: Add summary metrics

Calculate aggregated metrics for the weekly snapshot.

Python
def summary_metrics(results):
    ranked = [r for r in results if r['position']]
    improved = [r for r in results if r['change'] and r['change'] > 0]
    declined = [r for r in results if r['change'] and r['change'] < 0]
    ao_count = sum(1 for r in results if r['has_ao'])
    cited_count = sum(1 for r in results if r['cited'])
    avg_pos = sum(r['position'] for r in ranked) / len(ranked) if ranked else 0
    cost = len(results) * 0.005
    lines = []
    lines.append(f'\nSummary:')
    lines.append(f'  Ranked keywords: {len(ranked)}/{len(results)}')
    lines.append(f'  Average position: {avg_pos:.1f}')
    lines.append(f'  Improved: {len(improved)} | Declined: {len(declined)}')
    lines.append(f'  AI Overview coverage: {ao_count}/{len(results)} ({ao_count/len(results)*100:.0f}%)')
    lines.append(f'  Brand cited in AO: {cited_count}/{len(results)} ({cited_count/len(results)*100:.0f}%)')
    lines.append(f'  Report cost: ${cost:.3f}')
    return '\n'.join(lines)

print(summary_metrics(results))

Step 4: Package as a runnable script

Create the script that Claude Code skill can execute.

Python
def run_weekly_report():
    """Main entry point for /weekly-report skill."""
    print('Generating weekly SEO report...')
    print(f'Checking {len(KEYWORDS)} keywords for {MY_DOMAIN}\n')
    results = weekly_report()
    report = format_report(results)
    summary = summary_metrics(results)
    output = f'{report}{summary}'
    print(output)
    # Save to file for reference
    filename = f'report_{datetime.now().strftime("%Y%m%d")}.txt'
    with open(filename, 'w') as f:
        f.write(output)
    print(f'\nSaved to {filename}')
    return output

# Run: python weekly_report.py
# Or as Claude Code skill: /weekly-report
run_weekly_report()

Python Example

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

def report(keywords, domain):
    for kw in keywords:
        data = requests.post('https://api.scavio.dev/api/v1/search',
            headers=SH, json={'query': kw, 'country_code': 'us', 'include_ai_overview': True}).json()
        pos = next((r['position'] for r in data.get('organic_results', [])
            if domain in r.get('link', '')), None)
        ao = bool(data.get('ai_overview'))
        print(f'{kw:30} | #{pos or "-":>3} | AO: {"Y" if ao else "N"}')
    print(f'Cost: ${len(keywords) * 0.005:.3f}')

report(['serp api', 'search api python'], 'scavio.dev')

JavaScript Example

JavaScript
const SH = { 'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json' };
async function report(keywords, domain) {
  for (const kw of keywords) {
    const data = await fetch('https://api.scavio.dev/api/v1/search', {
      method: 'POST', headers: SH,
      body: JSON.stringify({ query: kw, country_code: 'us', include_ai_overview: true })
    }).then(r => r.json());
    const pos = (data.organic_results || []).find(r => (r.link||'').includes(domain))?.position;
    console.log(`${kw.padEnd(30)} | #${pos || '-'} | AO: ${data.ai_overview ? 'Y' : 'N'}`);
  }
}
report(['serp api', 'search api python'], 'scavio.dev').catch(console.error);

Expected Output

JSON
Generating weekly SEO report...
Checking 5 keywords for scavio.dev

Weekly SEO Report - 2026-05-19
Domain: scavio.dev
Keywords: 5

Keyword                        |  Pos |  Chg |  AO | Cited
------------------------------------------------------------
serp api                       |   #4 |   +1 |   Y |     Y
search api python              |   #7 |   -2 |   Y |     N
web scraping api               |   #6 |    0 |   N |     N
ai agent search tool           |   #3 |   +3 |   Y |     Y
mcp search server              |   #5 |  NEW |   Y |     N

Summary:
  Ranked keywords: 5/5
  Average position: 5.0
  Improved: 2 | Declined: 1
  AI Overview coverage: 4/5 (80%)
  Brand cited in AO: 2/5 (40%)
  Report cost: $0.025

Related Tutorials

  • How to Build a DIY Keyword Rank Tracker
  • How to Run a GEO/AEO Audit with a Search API
  • How to Track GEO Visibility with a Search API

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.

Claude Code CLI installed. Python 3.8+. A Scavio API key from scavio.dev. Target keywords to track. A Scavio API key gives you 50 free credits on signup.

Yes. The free tier includes 50 credits on signup, 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.

Related Resources

Use Case

Claude Code SEO Automation

Read more
Best Of

Best SEO Tools for Claude Code in 2026

Read more
Use Case

Weekly SEO Reporting with Claude Code

Read more
Glossary

Claude Code Skill for SEO

Read more
Best Of

Best API for Custom SEO Dashboards in 2026

Read more
Workflow

Daily Local Rank Tracking Pipeline

Read more

Start Building

Build a /weekly-report Claude Code skill that pulls SERP data, tracks rankings, and formats a dashboard. Python + Scavio API.

Get Free API KeyRead the Docs
ScavioScavio

Real-time search API for AI agents. Search every platform, not just Google.

Product

  • Features
  • Pricing
  • Dashboard
  • Affiliates

Developers

  • Documentation
  • API Reference
  • Quickstart
  • MCP Integration
  • Python SDK

Alternatives

  • Tavily Alternative
  • SerpAPI Alternative
  • Firecrawl Alternative
  • Exa Alternative

Tools

  • JSON Formatter
  • cURL to Code
  • Token Counter
  • All Tools

© 2026 Scavio. All rights reserved.

Featured on TAAFT
Terms of ServicePrivacy Policy