Skip to content
Doctor SEO

AI for SEO: Using the Models as Tools

Which AI for Which SEO Task: A Routing Table

A task-by-task routing table for the AI engines: which one to open first, the limit each is documented to hit, and the check that stays compulsory. Built from documented failures rather than vendor claims, with a runnable plan auditor.

Updated 3 October 2026 27 min read

Engine documentation is generous about capability: tool inventories, endpoints, authentication, knowledge cutoffs to the day. What it never tells you is how often any of this comes out right. No page cited below puts a number on that for any SEO task.

So the table below is built the opposite way round to a tool roundup: it starts from what is documented and lets the task find the engine. The cell a roundup fills first, which engine writes the better draft, is therefore left empty. The level is indexed at AI for SEO: Using the Models as Tools.

One thing this page is not about is being cited by the engines, which lives in the level that covers AI search citation; everything below is about operating them instead. Whether that other work amounts to a discipline of its own is an argument the industry is still having, and this page keeps out of it.

What you’ll learn

  • Read a routing table whose cells carry their evidence, and spot the one left empty.
  • Tell a documented condition from a failure seen once on one site, and weigh each accordingly.
  • Pick a route from the task, then test whether a metered search charge reverses it.
  • Name the five places a brand name is the right answer, and route the rest by attribute.
  • Audit a plan with route_check.py, including the one that looks clean and is not.

The cell the routing table leaves blank, and why

No page in the sources cited here states an accuracy figure for an SEO task, and that gap is worth seeing in the documents rather than taken on trust.

The Ahrefs connector page, stamped “Added January 2026” in its metadata, listed 61 tools on 20 September 2026 and nothing resembling a measure of correctness. The Semrush MCP documentation, updated 5 August 2026, sets out endpoints, transport and authentication, and states no accuracy for what it returns. Both are honest about what they promise, and neither promises accuracy.

Buyers name accuracy as the blocker. Keyword.com’s State of AI in SEO 2026: Survey Data on Automation, Tools & Time Saved, 1 January 2026, n=97 usable responses with 56% of respondents in teams of one to five, records 57% giving “quality not good enough” as the reason they held back from automating a task and 36% saying they did not trust the accuracy. Small and self-selected: an ordering of reasons, not a measurement of the profession.

The other cells get filled from two grades of evidence that should never be mixed. The first is a condition written into a vendor’s own documentation: a free-tier allowance, a knowledge cutoff, a language restriction. Anyone can open the page and confirm it, and it holds until the vendor changes it, so those cells carry a date, not a caveat.

The second grade is a failure somebody recorded while working. It shows the failure is possible and nothing about how often, so the denominator stays attached: Rich Voller’s five Screaming Frog MCP failure modes are dated 26 May 2026, one practitioner, one site, no sample size, and ContextBolt’s trial 23 June 2026, one site, one week. Every cell resting on a case says so.

What a route is actually keyed on

A route is decided by properties of the work, not by which product you pay for. Seven attributes separate the tasks below, each mapping to something an engine structurally has or lacks: an external metric from a collected dataset (volume, difficulty, referring domains), Google’s own result set, live conversation from X, cited sources to be located and opened, code output a validator can judge, confidentiality, which routes to a locally run open model and pays in hardware, or write access to a live page. If none is set, price decides.

The table

Each row names a task, the engine it goes to first, the condition or recorded failure putting it there, and the compulsory check. Dates are the source’s; every price and limit here was read 20 September 2026.

Task Engine you open first Documented limit or capability Compulsory check
Search volume, difficulty, referring-domain counts None, until a real data source is connected No general-purpose model holds a volume dataset, so the number is generated. ContextBolt recorded one treating third-party estimates as fact: 23 June 2026, one site Demand a source, a date, a method
Google’s own results for a list of queries Gemini, through Grounding with Google Search The one API among those cited here documented as running real Google queries and returning citation annotations. One request may bill as several searches Count billed searches, not prompts
Output that is code: JSON-LD, redirect rules, regular expressions ChatGPT or Claude No engine publishes an accuracy figure for generated code, and the one dated task-level comparison in the sources cited here ran four tasks across two engines and declared no rubric and no sample (SE Ranking, 7 July 2025) Validate every file before it ships
Locating the primary source behind a figure Perplexity Its measured demand framing is research: the perplexity vs chatgpt for probe of 20 September 2026 returned research, market research and academic research. The engine does not open the URL for you Open every cited URL and read the sentence
What is being said on X about an update today Grok, with its Web and X search tools on Grok 4.6 has no knowledge after 1 February 2026 without them (xAI documentation, 20 September 2026) Date every claim; conversation is not measurement
Driving your crawler from the chat window Screaming Frog’s native MCP server, v24.0, 19 May 2026 Five failure modes recorded by Rich Voller, 26 May 2026, among them an MCP-driven crawl that finished 500 URLs inside twenty seconds with the interface set to one URL a second. One practitioner’s write-up, one site, no sample size Poll to idle, then check timestamps against the rate set
Writing changes into a live CMS None in execute mode: it proposes, you apply The write-access failure in the sources cited here is two documented recurrences on two sites, which is a case and not a study (Search Engine Land, Will Scott, 28 August 2026) Dry run, backup, logged diff, human approval
Which engine writes the better draft Empty: no documented result None of the product pages cited here publishes an accuracy figure, an error rate or a test set for any SEO task Measure it on items you labelled yourself

One charge sits outside the table and can reverse a row that token price decided. On 20 September 2026 Gemini grounding gave 5,000 requests a month free and then charged $14 per 1,000, with OpenAI’s web search tool at $10 per 1,000 calls, both on top of tokens rather than inside them; Google’s documentation adds that “a customer-submitted request may result in one or more queries to Google Search. You will be charged for each individual search query performed.” A cheap model on a grounded route is not a cheap route, and the full cost model has its own lesson in this level.

Where a brand name is the right answer

Brand choice is defensible in one situation: the capability exists nowhere else. Five appear in the sources cited here, each a named feature rather than a task, routed below rather than built.

  • Custom GPTs with Actions, in ChatGPT. An OpenAPI schema pointed at a data provider does not transfer to another engine; Paul Shapiro published the canonical build against DataForSEO at Search Wilderness, 28 November 2023.
  • Grounding with Google Search, in Gemini. Real Google queries, with citation annotations returned alongside the text; nothing else in the sources cited here does it.
  • Perplexity Pages. A publishing surface inside an answer engine: Danny Goodwin reported at Search Engine Land, on a page carrying no date, that such pages have turned up in Google AI Overviews and featured snippets. Whether borrowing someone else’s domain is defensible belongs to the parasite SEO level.
  • Copilot inside Bing Webmaster Tools. Questions about your site answered from your own Webmaster Tools data, generally available since 18 March 2025 and English only, per the Bing Webmaster Blog. None of it generalises to Google.
  • Live X search, in Grok. X conversation as it happens: useful for the pulse of a discussion, and for no quantity at all.

What the autocomplete probes can order, and what they cannot

The reason to answer the engine question once rather than nine times is the demand measurement on the level hub: probed on 20 September 2026, three pairwise stems produced no SEO completion in thirty suggestions, while the two brand-free stems ran their full ten.

Autocomplete orders presence against absence and nothing else. That is enough to choose between one page and nine, but it cannot rank two stems that both run ten deep, it does not convert into a number of people, and an empty array means nothing surfaced rather than that nobody searches. Nor does it carry a volume, and since no named source attaches one to any of these stems, this page quotes none.

The drift cases are where the ordering earns its keep. On the same date copilot for seo managed two suggestions before turning to aviation; local llm for seo managed one before collapsing into Master of Laws degree programmes; grok for seo returned three, the third an unrelated HubSpot exam question; deepseek for seo gave two before sliding into how long SEO takes; gemini gem for seo went to star-sign gemstones. Even the healthier stems drift at six of ten, gemini for seo and perplexity for seo both to Seoul, while chatgpt deep research seo and openai api bulk seo returned nothing. A stem collapsing that fast is not carrying a page of its own, which is why Grok is a row in the table above, Copilot a capability rather than a route, and DeepSeek neither; locally run models are reached by the confidentiality attribute, not by a page.

Nine near-identical pages, and what the policy text says

Writing one page per engine rather than one table walks into an argument the industry has never resolved, and nothing below resolves it. The relevant text is in Google’s Search spam policies, stamped 28 August 2026: scaled content abuse is “many pages… generated for the primary purpose of manipulating search rankings and not helping users”, the first example given is “using generative AI tools or other similar tools to generate many pages without adding value for users”, and the qualifier is “no matter how it’s created”.

One camp reads a template filled in once per engine as matching that description directly: the pages exist because the keyword permutations exist, and the differentiating content is a product name. The other reads the policy as a conjunction rather than a count, and notes that it names purpose and value while publishing no number anywhere — no pages-per-day figure, no site-size ratio, no threshold. Neither side can point to a measurement in the sources cited here, where no study isolates a per-template split. Where the line falls is left open; this page is a table because of the demand measurement above.

Auditing a written plan before anything runs

A table routes one task; a quarter of planned work is thirty, and at thirty the failure is not the wrong engine but no check written down. route_check.py reads a plan as JSON carrying the seven attributes plus a batch size, returning accept, warn or reject per row. Standard library, no network, Python 3.11 or later.

It rejects a row contradicting something documented: an external metric routed to an engine, confidential data to a hosted one, Google’s results expected from an engine that cannot return them, write access with no dry run or approval, a check missing or too short. A warning means the route is allowed but the check is thin.

#!/usr/bin/env python3
# route_check.py - audits a written routing plan for SEO tasks against documented engine limits.
# doctor-seo.net, Level 8. Rules dated 20 September 2026. Standard library only, no network.
# Usage: python3 route_check.py              audits the built-in example plan
#        python3 route_check.py plan.json    audits your own plan
# Exit codes: 0 accepted, 2 rejected, 3 warnings only, 4 unusable input,
#             5 valid JSON but empty plan, 9 internal error (should never appear).

import json
import sys

ENGINES = ("none", "claude", "chatgpt", "gemini-grounding",
           "perplexity", "grok", "copilot-bwt", "local")

FLAGS = ("needs_external_metric", "needs_google_results", "needs_live_x",
         "needs_cited_sources", "code_output", "confidential", "writes_to_site")

# A check shorter than this is a placeholder, not a verification step.
MIN_CHECK = 15

EXAMPLE = {
    "plan": [
        {"task": "Search volumes and difficulty for 300 keywords",
         "engine": "chatgpt", "check": "Spot-check ten of them",
         "needs_external_metric": True, "needs_google_results": False,
         "needs_live_x": False, "needs_cited_sources": False,
         "code_output": False, "confidential": False,
         "writes_to_site": False, "batch_size": 300},
        {"task": "Collect Google's own results for 50 queries",
         "engine": "gemini-grounding",
         "check": "Count billed searches, not requests",
         "needs_external_metric": False, "needs_google_results": True,
         "needs_live_x": False, "needs_cited_sources": False,
         "code_output": False, "confidential": False,
         "writes_to_site": False, "batch_size": 50},
        {"task": "What is being said on X about the latest update",
         "engine": "grok", "check": "Treat it as conversation, not measurement",
         "needs_external_metric": False, "needs_google_results": False,
         "needs_live_x": True, "needs_cited_sources": False,
         "code_output": False, "confidential": False,
         "writes_to_site": False, "batch_size": 1},
        {"task": "Server logs for a client under a confidentiality agreement",
         "engine": "local", "check": "Confirm no prompt leaves the network",
         "needs_external_metric": False, "needs_google_results": False,
         "needs_live_x": False, "needs_cited_sources": False,
         "code_output": False, "confidential": True,
         "writes_to_site": False, "batch_size": 1},
        {"task": "Rewrite 4,000 product-page titles",
         "engine": "claude", "check": "Read a sample before any of it ships",
         "needs_external_metric": False, "needs_google_results": False,
         "needs_live_x": False, "needs_cited_sources": False,
         "code_output": False, "confidential": False,
         "writes_to_site": False, "batch_size": 4000},
        {"task": "Push the rewritten titles into the CMS",
         "engine": "claude", "check": "Dry run first, then human approval",
         "needs_external_metric": False, "needs_google_results": False,
         "needs_live_x": False, "needs_cited_sources": False,
         "code_output": False, "confidential": False,
         "writes_to_site": True, "batch_size": 4000},
    ]
}


def audit(row):
    """Return (rejects, warnings) for one routing."""
    rejects, warns = [], []
    engine = row["engine"]
    check = row["check"].strip()
    low = check.lower()

    if not check:
        rejects.append("no verification step written for this task")
    elif len(check) < MIN_CHECK:
        rejects.append("the check is %d characters long, which is a "
                       "placeholder rather than a verification step"
                       % len(check))
    if row["needs_external_metric"] and engine != "none":
        rejects.append("no engine holds search volume or difficulty data; "
                       "route to none and connect a real source")
    if row["confidential"] and engine != "local":
        rejects.append("confidential data routed to a hosted engine; "
                       "use local")
    if row["needs_google_results"] and engine != "gemini-grounding":
        rejects.append("only gemini-grounding returns Google's own results")
    if row["writes_to_site"] and "dry run" not in low and "approval" not in low:
        rejects.append("write access with no dry run and no approval gate "
                       "in the check")

    if row["needs_live_x"] and engine == "grok" and "2026" not in check:
        warns.append("date the claim: Grok 4.6 had no knowledge after "
                     "1 February 2026 without its search tools "
                     "(xAI docs, 20 September 2026)")
    if row["needs_cited_sources"] and "open" not in low and "read" not in low:
        warns.append("no engine verifies the citation it returns; the check "
                     "has to say the URLs get opened")
    if row["code_output"] and "validat" not in low:
        warns.append("no engine publishes an accuracy figure for generated "
                     "code; the check has to name a validator")
    if row["batch_size"] >= 500 and "sample" not in low:
        warns.append("batch of %d with no sampling in the check"
                     % row["batch_size"])
    if not any(row[f] for f in FLAGS) and engine != "none":
        warns.append("no attribute distinguishes the engines here, so this "
                     "is a cost choice, not a routing one")
    return rejects, warns


def validate(doc):
    """Raise ValueError on anything that is not a usable plan."""
    if not isinstance(doc, dict) or "plan" not in doc:
        raise ValueError("top level must be an object with a 'plan' key")
    plan = doc["plan"]
    if not isinstance(plan, list):
        raise ValueError("'plan' must be a list")
    for i, row in enumerate(plan, start=1):
        if not isinstance(row, dict):
            raise ValueError("row %d is not an object" % i)
        for key in ("task", "engine", "check"):
            if not isinstance(row.get(key), str):
                raise ValueError("row %d: '%s' must be a string" % (i, key))
        if row["engine"] not in ENGINES:
            raise ValueError("row %d: engine '%s' is not one of %s"
                             % (i, row["engine"], ", ".join(ENGINES)))
        for key in FLAGS:
            if not isinstance(row.get(key), bool):
                raise ValueError("row %d: '%s' must be true or false" % (i, key))
        size = row.get("batch_size")
        if isinstance(size, bool):
            raise ValueError("row %d: 'batch_size' must be a whole number, "
                             "and a boolean is not one" % i)
        if not isinstance(size, int) or size < 0:
            raise ValueError("row %d: 'batch_size' must be a whole number "
                             "of zero or more" % i)
    return plan


def is_empty(plan):
    """A plan with no rows, or no row carrying both a task and a check."""
    if not plan:
        return True
    return not any(row["task"].strip() and row["check"].strip()
                   for row in plan)


def run(path):
    if path is None:
        doc = EXAMPLE
        source = "built-in example plan"
    else:
        try:
            with open(path, encoding="utf-8") as fh:
                doc = json.load(fh)
        except UnicodeDecodeError as err:
            print("%s is not UTF-8 text: %s" % (path, err), file=sys.stderr)
            return 4
        except OSError as err:
            print("cannot read %s: %s" % (path, err), file=sys.stderr)
            return 4
        except json.JSONDecodeError as err:
            print("%s is not valid JSON: %s" % (path, err), file=sys.stderr)
            return 4
        source = path

    try:
        plan = validate(doc)
    except ValueError as err:
        print("%s is not a usable plan: %s" % (source, err), file=sys.stderr)
        return 4

    if is_empty(plan):
        print("%s parses but carries nothing to audit: %d rows, none with "
              "both a task and a check. This is not a clean pass."
              % (source, len(plan)), file=sys.stderr)
        return 5

    print("routing audit of %s" % source)
    print("%-3s %-8s %-17s %s" % ("#", "VERDICT", "ENGINE", "TASK"))
    rejected = warned = 0
    for i, row in enumerate(plan, start=1):
        rejects, warns = audit(row)
        verdict = "REJECT" if rejects else ("WARN" if warns else "ACCEPT")
        if rejects:
            rejected += 1
        elif warns:
            warned += 1
        print("%-3d %-8s %-17s %s" % (i, verdict, row["engine"], row["task"]))
        for note in rejects + warns:
            print("    -> %s" % note)

    accepted = len(plan) - rejected - warned
    print("--")
    print("%d routing%s: %d accepted, %d warned, %d rejected"
          % (len(plan), "" if len(plan) == 1 else "s",
             accepted, warned, rejected))
    if rejected:
        return 2
    if warned:
        return 3
    return 0


def main():
    path = sys.argv[1] if len(sys.argv) > 1 else None
    return run(path)


if __name__ == "__main__":
    try:
        sys.exit(main())
    except Exception as err:                      # noqa: BLE001
        print("internal error, nothing was audited: %r" % err, file=sys.stderr)
        sys.exit(9)

The exit codes do not overlap, and note what is missing: 1. Python exits 1 on an uncaught exception, so a script using 1 for findings reports its own crash as a finding about your plan. Everything is caught and sent to 9 instead.

Code Meaning
0 Every routing accepted
2 At least one routing rejected
3 Warnings only, nothing rejected
4 Input unreadable, not UTF-8, not valid JSON, or not a usable plan
5 Valid JSON, but the plan carries nothing to audit
9 Internal error; nothing was audited

Code 5 exists for the input that looks fine: rows present, task and check blank, parses, scores nothing, and to anything reading the exit status it looks clean. A two-character check is the same failure wearing content, so any check under fifteen characters is rejected. batch_size must likewise be a whole number that is not a boolean, because in Python True passes an integer test. The seven fixtures below reproduce every run but the first, which uses the built-in plan.

--- clean.json (three rows, every attribute matched by its check) ---
{"plan": [
  {"task": "Search volumes for 300 keywords", "engine": "none",
   "check": "Pull them from a connected data source, with its export date",
   "needs_external_metric": true, "needs_google_results": false,
   "needs_live_x": false, "needs_cited_sources": false, "code_output": false,
   "confidential": false, "writes_to_site": false, "batch_size": 300},
  {"task": "Find the primary source behind a cited figure", "engine": "perplexity",
   "check": "Open every cited URL and read the sentence it points at",
   "needs_external_metric": false, "needs_google_results": false,
   "needs_live_x": false, "needs_cited_sources": true, "code_output": false,
   "confidential": false, "writes_to_site": false, "batch_size": 12},
  {"task": "Generate JSON-LD for 40 product pages", "engine": "claude",
   "check": "Validate every file in Google's Rich Results Test",
   "needs_external_metric": false, "needs_google_results": false,
   "needs_live_x": false, "needs_cited_sources": false, "code_output": true,
   "confidential": false, "writes_to_site": false, "batch_size": 40}
]}
--- warn.json (allowed routes, thin checks) ---
{"plan": [
  {"task": "Find the primary source behind a cited figure", "engine": "perplexity",
   "check": "Keep whichever citation looks most official",
   "needs_external_metric": false, "needs_google_results": false,
   "needs_live_x": false, "needs_cited_sources": true, "code_output": false,
   "confidential": false, "writes_to_site": false, "batch_size": 12},
  {"task": "Generate hreflang blocks for 1,200 URLs", "engine": "chatgpt",
   "check": "Eyeball the first few and ship",
   "needs_external_metric": false, "needs_google_results": false,
   "needs_live_x": false, "needs_cited_sources": false, "code_output": true,
   "confidential": false, "writes_to_site": false, "batch_size": 1200}
]}
--- boolbatch.json (batch_size is a boolean) ---
{"plan": [
  {"task": "Rewrite 900 category descriptions", "engine": "chatgpt",
   "check": "Read a sample of 50 before anything ships",
   "needs_external_metric": false, "needs_google_results": false,
   "needs_live_x": false, "needs_cited_sources": false, "code_output": false,
   "confidential": false, "writes_to_site": false, "batch_size": true}
]}
--- broken.json (not valid JSON: the object is never closed) ---
{"plan": [ {"task": "Audit 120 title tags", "engine": "claude",
--- latin1.json (the same shape, saved in Latin-1 rather than UTF-8) ---
python3 -c 'open("latin1.json","wb").write(b"{\"plan\": [{\"task\": \"Rewrite 40 caf\xe9 listings\"}]}")'
--- hollow.json (valid JSON, rows present, nothing in them) ---
{"plan": [
  {"task": "", "engine": "claude", "check": "",
   "needs_external_metric": false, "needs_google_results": false,
   "needs_live_x": false, "needs_cited_sources": false, "code_output": false,
   "confidential": false, "writes_to_site": false, "batch_size": 0},
  {"task": "   ", "engine": "chatgpt", "check": "",
   "needs_external_metric": false, "needs_google_results": false,
   "needs_live_x": false, "needs_cited_sources": false, "code_output": false,
   "confidential": false, "writes_to_site": false, "batch_size": 0}
]}
--- thin.json (valid JSON, a real task, a check that is not one) ---
{"plan": [
  {"task": "Cluster 2,000 exported keywords", "engine": "claude", "check": "ok",
   "needs_external_metric": false, "needs_google_results": false,
   "needs_live_x": false, "needs_cited_sources": false, "code_output": false,
   "confidential": false, "writes_to_site": false, "batch_size": 40}
]}

Eight runs follow, landing on five of the six codes; 9 appears in none, which is the point of it. Runs four to six all return 4: a boolean where a count belongs, truncated JSON, a file that is not UTF-8.

$ python3 route_check.py 
routing audit of built-in example plan
#   VERDICT  ENGINE            TASK
1   REJECT   chatgpt           Search volumes and difficulty for 300 keywords
    -> no engine holds search volume or difficulty data; route to none and connect a real source
2   ACCEPT   gemini-grounding  Collect Google's own results for 50 queries
3   WARN     grok              What is being said on X about the latest update
    -> date the claim: Grok 4.6 had no knowledge after 1 February 2026 without its search tools (xAI docs, 20 September 2026)
4   ACCEPT   local             Server logs for a client under a confidentiality agreement
5   WARN     claude            Rewrite 4,000 product-page titles
    -> no attribute distinguishes the engines here, so this is a cost choice, not a routing one
6   WARN     claude            Push the rewritten titles into the CMS
    -> batch of 4000 with no sampling in the check
--
6 routings: 2 accepted, 3 warned, 1 rejected
exit 2

$ python3 route_check.py clean.json
routing audit of clean.json
#   VERDICT  ENGINE            TASK
1   ACCEPT   none              Search volumes for 300 keywords
2   ACCEPT   perplexity        Find the primary source behind a cited figure
3   ACCEPT   claude            Generate JSON-LD for 40 product pages
--
3 routings: 3 accepted, 0 warned, 0 rejected
exit 0

$ python3 route_check.py warn.json
routing audit of warn.json
#   VERDICT  ENGINE            TASK
1   WARN     perplexity        Find the primary source behind a cited figure
    -> no engine verifies the citation it returns; the check has to say the URLs get opened
2   WARN     chatgpt           Generate hreflang blocks for 1,200 URLs
    -> no engine publishes an accuracy figure for generated code; the check has to name a validator
    -> batch of 1200 with no sampling in the check
--
2 routings: 0 accepted, 2 warned, 0 rejected
exit 3

$ python3 route_check.py boolbatch.json
boolbatch.json is not a usable plan: row 1: 'batch_size' must be a whole number, and a boolean is not one
exit 4

$ python3 route_check.py broken.json
broken.json is not valid JSON: Expecting property name enclosed in double quotes: line 2 column 1 (char 64)
exit 4

$ python3 route_check.py latin1.json
latin1.json is not UTF-8 text: 'utf-8' codec can't decode byte 0xe9 in position 34: invalid continuation byte
exit 4

$ python3 route_check.py hollow.json
hollow.json parses but carries nothing to audit: 2 rows, none with both a task and a check. This is not a clean pass.
exit 5

$ python3 route_check.py thin.json
routing audit of thin.json
#   VERDICT  ENGINE            TASK
1   REJECT   claude            Cluster 2,000 exported keywords
    -> the check is 2 characters long, which is a placeholder rather than a verification step
    -> no attribute distinguishes the engines here, so this is a cost choice, not a routing one
--
1 routing: 0 accepted, 0 warned, 1 rejected
exit 2

Run seven is the quiet one: nothing audited, nothing on standard output, only the exit status saying so.

Where this page stops

A route is not a decision to delegate. That question has a different method and its own lesson in this level, and everything above assumes it is behind you. Keyword.com’s survey sizes what is left for people at 1 respondent in 97 calling any workflow fully automated (1 January 2026, n=97), and what Claude can and cannot do for SEO works one engine’s scope through, task by task.

Nor does a route say anything about visibility: nothing in the sources cited here connects the engine a page was drafted with to whether an answer engine later cites it, and that subject sits outside this level anyway. Two routes carry lessons rather than table rows — your own performance data, where no official Search Console connector exists (Passionfruit, 28 March 2026, listed it as community or self-hosted), in connecting Claude to Search Console and GA4 over MCP, and write access, in Claude, WordPress and Rank Math.

And no route rescues a number nobody measured. The data exists only inside the products the Ahrefs and Semrush connectors sell access to; an engine with no such feed composes a figure instead — Si Quan Ong, writing for Ahrefs on 30 October 2025, called what models quote guesswork. So the first row refuses rather than recommends, and the problem has its own treatment elsewhere in this level.

Common mistakes

  • Routing by house loyalty. Teams paying for one engine send it everything, including what it cannot do. The fix: run the seven attributes first; if none is set, the engine is a price decision.
  • A routing plan with no check column. Naming engines but not verifications answers half the question. The fix: write the check before the engine; the script rejects a missing or placeholder check.
  • Promoting one recorded case into a rate. Five hundred URLs inside twenty seconds against a one-per-second setting is a single case documented in the sources cited here, one site, no sample size published (Rich Voller, 26 May 2026). The fix: reproduce single cases on staging; never quote them as frequencies.
  • Sending confidentiality-bound data to a hosted engine because a local model is slower. Speed is a preference; the constraint is contractual. The fix: route locally, budget hardware.

The short version

  • One cell is blank on purpose: no product page cited here states how often any engine is right at any SEO task. Buyers say that is the problem — 57% named quality as the reason they held back, 36% distrust of accuracy (Keyword.com, 1 January 2026, n=97, small and self-selected).
  • route_check.py audits a plan before any of it runs, returning 0, 2, 3, 4, 5 or 9 and rejecting checks that are blank or placeholders rather than waving them through.
  • Route on the task, not the brand: external metric, Google’s own results, live X conversation, cited sources, code output, confidentiality, write access. If none applies, price decides, and none of it answers whether to delegate at all.
  • Filled cells carry one of two grades of evidence, never mixed: a condition in a vendor’s documentation, re-checkable by anyone, or a failure recorded once on one site, which proves possibility, not frequency.
  • A metered search charge can reverse a row that token price decided, and Google documents that one submitted request may bill as several searches.
  • Five capabilities in the sources cited here live in one engine each, and those are the only places a brand name is the answer. Otherwise the question is asked once, because the pairwise comparison stems probed on 20 September 2026 produced no SEO completion.
  • What this page refuses to decide: where Google’s scaled content line falls, and whether being cited by AI search is a discipline of its own. Both are live arguments, settled neither way here.

Frequently asked questions

Does the routing change if I only pay for one engine?

Partly. Four routes turn on a capability that exists in one engine only — Google’s own results from Gemini grounding, live X conversation from Grok, your Bing data from Copilot in Bing Webmaster Tools, an Actions-backed feed from a custom GPT — and no subscription substitutes for those. None of them fixes the first row, where an external metric needs a connected data source.

Can I fill the empty cell myself?

Yes, and it is the only way that cell gets filled: the measurement is about your content rather than the market, and nobody else can make it for you. It will not generalise to another site, and it does not need to. Doing it rigorously has a lesson of its own in this level.

The table sends code to ChatGPT or Claude. Is that not a brand choice?

It is the weakest row, and marked that way: no engine in the sources cited here publishes an accuracy figure for generated code. What the row really says is that the output is machine-checkable, so the validator carries the risk instead of the brand, and once the file has passed Google’s Rich Results Test the choice stops mattering.

Sources

  • Google Autocomplete probes, 20 September 2026: three comparison stems, thirty suggestions, no SEO completion; two brand-free stems, ten each; four engine stems drifting within two or three; two empty.
  • Keyword.com, State of AI in SEO 2026: Survey Data on Automation, Tools & Time Saved, 1 January 2026, n=97, 56% in teams of one to five (keyword.com): the 57%, 36% and 1 in 97.
  • SE Ranking, Yulia Deda, Claude AI vs. ChatGPT: Which AI Tool is Better for SEO?, 7 July 2025, updated 24 June 2026 (seranking.com): four tasks, no rubric or sample. ContextBolt, author given only as David, Claude SEO Experiment, 23 June 2026 (contextbolt.com): one site, one week. Rich Voller, Screaming Frog v24 MCP, 26 May 2026, upd. 12 June 2026 (richvoller.com): five failure modes, one site, no sample size; v24.0 shipped the native MCP server 19 May 2026.
  • Ahrefs connector page (claude.com), 61 tools on 20 September 2026, metadata stamp “Added January 2026”; Semrush MCP documentation, 5 August 2026 (developer.semrush.com); Passionfruit, Dewang Mishra, 28 March 2026 (getpassionfruit.com). None states an accuracy figure. Paul Shapiro, Using ChatGPT’s Custom “GPTs”, Search Wilderness, 28 November 2023 (searchwilderness.com).
  • Named and dated without a link, because none of these pages is among those linked above: Si Quan Ong, AI can’t replace SEO tools, but it can use them, Ahrefs, 30 October 2025; Will Scott, Use Claude for SEO. Don’t let Claude do SEO., Search Engine Land, 28 August 2026 — the duplication recurred on a second site, so a case and not a study; Danny Goodwin on Perplexity Pages in AI Overviews and featured snippets, Search Engine Land, no date on the page; Copilot in Bing Webmaster Tools, Bing Webmaster Blog, 18 March 2025.
  • Google Search Central, Spam policies for Google web search, 28 August 2026: the text quoted above, publishing no page threshold. Pricing and the billed-search caveat from the official documentation of Google AI for Developers, OpenAI and xAI, 20 September 2026 — list prices, not measurements.

Continue the course

The previous lesson, Claude and Your CMS: WordPress, Rank Math, and Write Access, turns the write-access row above into a protocol. Back up one level for AI for SEO: Using the Models as Tools, or two for the whole SEO course at doctor-seo.net.