Skip to content

Full Merlino Audit SOP Crown Jewel

This is the master SOP for running a complete Merlino-style SEO audit. No stone unturned. Every endpoint listed. Every data point pulled. Agents reference this page when executing an audit.

Audit cost estimate: $12–$22 per client domain (depends on backlink index size and crawl depth)

Execution time: 8–15 minutes (with parallel execution)

Output format: Static TypeScript files at clients/{slug}/ consumed by the audit dashboard


Pre-Audit Checklist

  • [ ] Confirm DATAFORSEO_LOGIN and DATAFORSEO_PASSWORD env vars are set
  • [ ] Confirm GOOGLE_PLACES_API_KEY is set (for GMB verification)
  • [ ] Collect: target domain, 4–6 target keywords, all GMB business names and CIDs
  • [ ] Confirm client location (city, state, zip) for local SERP accuracy
  • [ ] Set location_code: 2840 (US) or override to specific metro code
  • [ ] Confirm output directory: clients/{slug}/

Execution Groups

Run all calls within a group in parallel. Wait for each group to complete before starting the next.

Group 1 (parallel): Backlinks Summary + Labs Domain Overview + Domain Technologies + WHOIS + Content Analysis Summary
Group 2 (parallel): All SERP calls (4 target keywords × organic + maps + local finder + news + images + AI overview)
Group 3 (parallel): Keywords Data (volume + keywords for site + keywords for keywords + trends)
Group 4 (parallel): Labs (ranked keywords + competitors + historical rank overview + bulk traffic + keyword difficulty + search intent)
Group 5 (parallel): Backlinks Detail (backlinks list + anchors + referring domains + domain pages + timeseries)
Group 6 (parallel): Business Data (GMB info + reviews for each location)
Group 7 (sequential): On-Page crawl task post → poll for completion → retrieve pages + errors + links + duplicates
Group 8 (parallel): AI Optimization (LLM mentions brand + service keywords, ChatGPT/Claude/Gemini/Perplexity responses)

DataForSEO Pulls

All backlinks endpoints are Live-only. Run as early as possible — largest dataset.

#EndpointPayload Key FieldsCost Est.Group
1POST /v3/backlinks/summary/livetarget, include_subdomains: true~$0.011
2POST /v3/backlinks/history/livetarget, date_from, date_to~$0.015
3POST /v3/backlinks/backlinks/livetarget, limit: 100, order_by: ["rank,desc"]~$0.02–$0.055
4POST /v3/backlinks/anchors/livetarget, limit: 50~$0.0055
5POST /v3/backlinks/referring_domains/livetarget, limit: 100, order_by: ["rank,desc"]~$0.01–$0.055
6POST /v3/backlinks/domain_pages/livetarget, limit: 50~$0.015
7POST /v3/backlinks/domain_pages_summary/livetarget, limit: 50~$0.011
8POST /v3/backlinks/referring_networks/livetarget, limit: 50~$0.015
9POST /v3/backlinks/competitors/livetarget, limit: 10~$0.015
10POST /v3/backlinks/bulk_spam_score/livetargets: [competitor_domains]~$0.002/domain5
11POST /v3/backlinks/bulk_ranks/livetargets: [domain, competitors...]~$0.002/domain1
12POST /v3/backlinks/timeseries_referring_domains/livetarget, date_from, date_to, group_by: "month"~$0.015

Python — Backlinks Summary:

python
import os, requests
from requests.auth import HTTPBasicAuth

auth = HTTPBasicAuth(os.environ["DATAFORSEO_LOGIN"], os.environ["DATAFORSEO_PASSWORD"])
BASE = "https://api.dataforseo.com"

def backlinks_summary(target):
    r = requests.post(
        f"{BASE}/v3/backlinks/summary/live",
        auth=auth,
        headers={"Content-Type": "application/json"},
        json=[{"target": target, "include_subdomains": True}]
    )
    return r.json()["tasks"][0]["result"][0]

SERP API — All Tabs, 4+ Keywords

Run for each target keyword. Cover every SERP tab.

#EndpointWhen to UseCost Est.Group
1POST /v3/serp/google/organic/live/advancedAll 4+ target keywords~$0.005/kw2
2POST /v3/serp/google/maps/live/advancedPrimary local query (city + service)~$0.0032
3POST /v3/serp/google/local_finder/live/advancedSame local query~$0.0032
4POST /v3/serp/google/news/live/advancedBrand name query~$0.0032
5POST /v3/serp/google/images/live/advancedBrand name + primary service query~$0.0032
6POST /v3/serp/google/ai_overview/livePrimary service keyword~$0.0052
7POST /v3/serp/google/ai_mode/tasks_postPrimary service keyword (async)~$0.012
8GET /v3/serp/google/autocomplete/live/advancedBrand name, primary service~$0.0012

Standard organic SERP payload:

python
def serp_organic(keyword, location_code=2840, language_code="en", depth=20):
    r = requests.post(
        f"{BASE}/v3/serp/google/organic/live/advanced",
        auth=auth,
        headers={"Content-Type": "application/json"},
        json=[{
            "keyword": keyword,
            "location_code": location_code,
            "language_code": language_code,
            "depth": depth,
            "calculate_rectangles": False
        }]
    )
    return r.json()["tasks"][0]["result"][0]

Keywords Data API — 5 Endpoints

#EndpointPayloadCost Est.Group
1POST /v3/keywords_data/google_ads/search_volume/liveAll target keywords (batch)~$0.0005/kw3
2POST /v3/keywords_data/google_ads/keywords_for_site/liveClient domain~$0.013
3POST /v3/keywords_data/google_ads/keywords_for_keywords/livePrimary service keywords as seeds~$0.0053
4POST /v3/keywords_data/google_trends/explore/livePrimary 2–3 keywords~$0.001/kw3
5POST /v3/keywords_data/clickstream/search_volume/liveAll target keywords~$0.001/kw3

DataForSEO Labs API — 9 Endpoints

#EndpointPayloadCost Est.Group
1POST /v3/dataforseo_labs/google/ranked_keywords/liveClient domain, limit: 100~$0.014
2POST /v3/dataforseo_labs/google/competitors_domain/liveClient domain, limit: 10~$0.014
3POST /v3/dataforseo_labs/google/domain_rank_overview/liveClient domain~$0.011
4POST /v3/dataforseo_labs/google/historical_rank_overview/liveClient domain~$0.014
5POST /v3/dataforseo_labs/google/keyword_suggestions/livePrimary service keyword~$0.0054
6POST /v3/dataforseo_labs/google/related_keywords/livePrimary service keyword~$0.0054
7POST /v3/dataforseo_labs/google/keyword_difficulty/liveAll target keywords (batch)~$0.002/kw4
8POST /v3/dataforseo_labs/google/search_intent/liveAll target keywords (batch)~$0.001/kw4
9POST /v3/dataforseo_labs/google/bulk_traffic_estimation/liveClient domain + top 5 competitors~$0.0054

AI Optimization API — 8+ Endpoint Calls

Brand and service keyword visibility across all major LLMs.

#What to CallEndpointCost Est.Group
1LLM Mentions — brand namePOST /v3/ai_optimization/llm_mentions/search/live~$0.0058
2LLM Mentions — primary service keywordPOST /v3/ai_optimization/llm_mentions/search/live~$0.0058
3LLM Mentions — secondary service keywordPOST /v3/ai_optimization/llm_mentions/search/live~$0.0058
4ChatGPT response — "best [service] in [city]"POST /v3/ai_optimization/chatgpt/live/tasks_post~$0.018
5Claude response — "best [service] in [city]"POST /v3/ai_optimization/claude/live/tasks_post~$0.018
6Gemini response — "best [service] in [city]"POST /v3/ai_optimization/gemini/live/tasks_post~$0.018
7Perplexity response — "best [service] in [city]"POST /v3/ai_optimization/perplexity/live~$0.018
8AI Keyword Data — primary keywordsPOST /v3/ai_optimization/ai_keyword_data/live~$0.0058

LLM Mentions payload:

python
def llm_mentions(keyword, location_code=2840):
    r = requests.post(
        f"{BASE}/v3/ai_optimization/llm_mentions/search/live",
        auth=auth,
        headers={"Content-Type": "application/json"},
        json=[{
            "keyword": keyword,
            "location_code": location_code,
            "language_code": "en"
        }]
    )
    return r.json()["tasks"][0]["result"][0]

On-Page API — Full Site Crawl

#StepEndpointCost Est.Group
1Start crawlPOST /v3/on_page/task_post~$0.004/page × pages7a
2Poll statusGET /v3/on_page/summary/{task_id}Free7b
3Get pagesPOST /v3/on_page/pages/{task_id}Free (included)7c
4Get errorsPOST /v3/on_page/errors/{task_id}Free7c
5Get linksPOST /v3/on_page/links/{task_id}Free7c
6Duplicate tagsPOST /v3/on_page/duplicate_tags/{task_id}Free7c
7Duplicate contentPOST /v3/on_page/duplicate_content/{task_id}Free7c
8ResourcesPOST /v3/on_page/resources/{task_id}Requires load_resources: true7c
9Instant page scanPOST /v3/on_page/instant_pages~$0.0047c
10Lighthouse auditPOST /v3/on_page/lighthouse/live/json~$0.027c

Crawl task payload:

python
def start_onpage_crawl(domain, max_pages=100):
    r = requests.post(
        f"{BASE}/v3/on_page/task_post",
        auth=auth,
        headers={"Content-Type": "application/json"},
        json=[{
            "target": domain,
            "max_crawl_pages": max_pages,
            "load_resources": False,
            "enable_javascript": False,
            "enable_browser_rendering": False,
            "store_raw_html": False,
            "check_spell": False
        }]
    )
    return r.json()["tasks"][0]["id"]

Business Data API — GMB + Reviews

Run for every client GMB location.

#EndpointPayloadCost Est.Group
1POST /v3/business_data/google/my_business_info/liveBusiness name + city~$0.01/location6
2POST /v3/business_data/google/reviews/task_postBusiness CID or name~$0.01–$0.026
3POST /v3/business_data/google/my_business_updates/task_postBusiness CID~$0.016

If client is in hospitality, add:

  • POST /v3/business_data/tripadvisor/reviews/task_post
  • POST /v3/business_data/trustpilot/reviews/task_post

Domain Analytics API — 2 Endpoints

#EndpointPayloadCost Est.Group
1POST /v3/domain_analytics/technologies/domain_technologies/liveClient domain + top 3 competitors~$0.005/domain1
2POST /v3/domain_analytics/whois/overview/liveClient domain~$0.0051

Content Analysis API — 3 Endpoints

#EndpointPayloadCost Est.Group
1POST /v3/content_analysis/search/liveBrand name, limit: 50~$0.0051
2POST /v3/content_analysis/summary/liveBrand name~$0.0021
3POST /v3/content_analysis/sentiment_analysis/liveBrand name~$0.0021

Non-DataForSEO Pulls

These run in parallel with Group 1 or Group 2.

#SourceWhat to PullHow
1Direct HTTPHomepage HTML — verify schema markup, meta tags, GHL/LeadConnector detectioncurl -s https://domain.com
2Google Places APIVerified GBP data — CID, categories, hours, photos countGET https://maps.googleapis.com/maps/api/place/details/json
3ReddAPIReddit brand mentions and sentimentGET https://api.reddapi.com/...
4DomDetailer APIDA, TF, CF, Trust Flow, Spam ScorePOST https://www.domdetailer.com/api/...
5Queen browser agentSocial media follower counts (Facebook, Instagram, YouTube)Browser visit — NEVER use scraped estimates
6Queen browser agentYouTube channel presence — subscribers, videos, last post dateBrowser visit
7Queen browser agentLSA (Local Services Ads) presence check — "Google Guaranteed" badgeBrowser visit + screenshot
8Queen browser agentGoogle Ads presence check — paid listings in SERP screenshotsBrowser visit
9Direct HTTProbots.txt, sitemap.xml — parse and validatecurl -s https://domain.com/robots.txt

Output Files (Static TypeScript)

Write these files to clients/{slug}/:

FileContents
audit.tsTop-level audit summary, score, action items, technologies
backlinks-data.tsBacklinks summary, referring domains, anchors, history, timeseries
serp-data.tsOrganic rankings, map pack, local finder, AI overview results
keywords.tsSearch volume, difficulty, intent, trends for all target keywords
onpage-data.tsCrawl summary, page issues, Lighthouse scores, errors
gmb-data.tsGMB info, reviews, ratings, Q&A for each location
llm-data.tsLLM mentions, AI engine responses, AI keyword data
competitors-data.tsCompetitor domains, traffic estimates, keyword overlap
geogrid-data.tsGeogrid rank positions (if geogrid service used)

All exports must be typed TypeScript — no any, no untyped objects. Dashboard components import directly.


Cost Estimate by Category

CategoryEndpointsCost Range
Backlinks (12 endpoints)summary, history, backlinks, anchors, domains, pages, networks, competitors, bulk spam, bulk ranks, timeseries$0.15–$0.50
SERP (8 calls × 4 keywords)organic, maps, local finder, news, images, AI overview, AI mode, autocomplete$0.20–$0.40
Keywords Data (5 endpoints)volume, for site, for keywords, trends, clickstream$0.05–$0.10
Labs (9 endpoints)ranked keywords, competitors, rank overview, historical, suggestions, related, difficulty, intent, traffic$0.10–$0.15
On-Page (crawl 50 pages)task post, summary, pages, errors, links, dupes, Lighthouse$0.20–$0.40
Business Data (2 locations)GMB info, reviews per location$0.04–$0.08
Domain Analyticstechnologies, WHOIS$0.01–$0.02
Content Analysissearch, summary, sentiment$0.01
AI Optimization (8 calls)LLM mentions × 3, LLM responses × 4, AI keyword data$0.07–$0.10
Total~48 DataForSEO calls$0.83–$1.75

Plus non-DataForSEO: Google Places (~$0.01), DomDetailer (~$0.01), Queen browser time (~$0).

Total per full audit: approximately $1–$2 in API costs.


QA Checklist — 3 Rounds

Round 1: Data Completeness

  • [ ] All 48+ DataForSEO calls returned status_code: 20000
  • [ ] No None or empty result arrays in critical fields
  • [ ] All TypeScript files compile without errors (tsc --noEmit)
  • [ ] All export names match what dashboard components import
  • [ ] backlinks-data.ts has real integer values for backlinks, referring_domains, rank
  • [ ] keywords.ts has real integer search_volume (not strings, not 0 for all)
  • [ ] serp-data.ts has at least 1 organic result for each keyword
  • [ ] gmb-data.ts has correct business name, address, rating, review count
  • [ ] llm-data.ts has llm_mentions entries with se_type field

Round 2: Data Accuracy

  • [ ] Domain rank (DataForSEO) matches DomDetailer DA within 20%
  • [ ] Review count in GMB data matches visible Google count (Queen verifies)
  • [ ] Top-ranking keywords in ranked_keywords match known real rankings
  • [ ] Social follower counts are browser-verified — NOT from any API estimate
  • [ ] Schema markup detected on homepage matches what's actually in the HTML

Round 3: Dashboard Render

  • [ ] npm run build passes 30/30 pages with 0 errors
  • [ ] All 7 dashboard tabs render without blank panels
  • [ ] Recharts / chart components have data (not empty arrays)
  • [ ] No console errors in browser (Queen screenshot)
  • [ ] All numbers visible in UI match source TypeScript data

Parallel Execution Script (Python Skeleton)

python
import os
import asyncio
import aiohttp
from aiohttp import BasicAuth

LOGIN = os.environ["DATAFORSEO_LOGIN"]
PASSWORD = os.environ["DATAFORSEO_PASSWORD"]
BASE = "https://api.dataforseo.com"

auth = BasicAuth(LOGIN, PASSWORD)

async def post(session, path, payload):
    async with session.post(f"{BASE}{path}", json=payload) as r:
        return await r.json()

async def run_group(session, calls):
    """Run a list of (path, payload) tuples in parallel."""
    tasks = [post(session, path, payload) for path, payload in calls]
    return await asyncio.gather(*tasks)

async def full_audit(domain, keywords, location_code=2840):
    async with aiohttp.ClientSession(auth=auth, headers={"Content-Type": "application/json"}) as session:

        # Group 1 — Domain-level data
        group1 = [
            ("/v3/backlinks/summary/live", [{"target": domain}]),
            ("/v3/backlinks/domain_pages_summary/live", [{"target": domain, "limit": 50}]),
            ("/v3/backlinks/bulk_ranks/live", [{"targets": [domain]}]),
            ("/v3/dataforseo_labs/google/domain_rank_overview/live", [{"target": domain, "location_code": location_code, "language_code": "en"}]),
            ("/v3/domain_analytics/technologies/domain_technologies/live", [{"target": domain}]),
            ("/v3/domain_analytics/whois/overview/live", [{"target": domain}]),
            ("/v3/content_analysis/summary/live", [{"keyword": domain.replace(".com", "")}]),
            ("/v3/content_analysis/sentiment_analysis/live", [{"keyword": domain.replace(".com", "")}]),
        ]
        results_g1 = await run_group(session, group1)

        # Group 2 — SERP for all keywords
        group2 = [
            ("/v3/serp/google/organic/live/advanced", [{"keyword": kw, "location_code": location_code, "language_code": "en", "depth": 20}])
            for kw in keywords
        ] + [
            ("/v3/serp/google/maps/live/advanced", [{"keyword": keywords[0] + " near me", "location_code": location_code, "language_code": "en"}]),
            ("/v3/serp/google/local_finder/live/advanced", [{"keyword": keywords[0], "location_code": location_code, "language_code": "en"}]),
            ("/v3/serp/google/ai_overview/live", [{"keyword": keywords[0], "location_code": location_code, "language_code": "en"}]),
        ]
        results_g2 = await run_group(session, group2)

        # ... continue for groups 3-8
        return {"group1": results_g1, "group2": results_g2}

if __name__ == "__main__":
    domain = "example.com"
    keywords = ["roofing contractor dallas", "roof replacement dallas", "emergency roof repair", "metal roof installation"]
    asyncio.run(full_audit(domain, keywords))

Key Rules

  1. Never use search snippets or scraped estimates for data — DataForSEO API is the mandatory primary source for all SEO metrics.
  2. Social follower counts must be browser-verified by Queen — no API ever returns accurate follower counts.
  3. All TypeScript output must be static — no API calls from the dashboard. Data is pre-fetched and committed to the repo.
  4. Mark clearly when data is unavailable — if an endpoint returns no result, set the field to null and add a comment. Never fabricate values.
  5. Retry each failed call once — if still failing after retry, log the error and continue. Don't let one bad endpoint block the entire audit.
  6. Cost logging is mandatory — log data.cost from every response. Alert if any single call exceeds $0.50.

Internal SOP reference — not affiliated with DataForSEO.