How to Set Up Codex for Google Search Data (Full SKILL.md Included)

Key takeaways

A Codex skill that reads Google's own data with zero API keys: analyze a Search Console export for totals, CTR bands, and quick wins at position 4-10, plus the curl commands and thresholds for PageSpeed and CrUX field data.

Every crawl-based audit you will ever run shares one quiet assumption: that what a computer can fetch looks like Google's view of the page. It is wrong in a specific, avoidable way. A crawler sees HTML; Google sees HTML plus 28 days of real Chrome users, plus indexation state that no crawler can infer, plus search performance that nobody outside Google can compute. This article's skill pulls you into that second world - and the best part is that the main path needs no API key, no Google account setup, and no credentials at all. If a Search Console export exists, one python3 script reads it.

Google's data is a different category of truth

Lab data is a snapshot of one machine at one moment. Field data is Chrome users, aggregated over 28 days at the 75th percentile (p75). The difference decides how you fix pages: a Lighthouse score of 92 on your laptop means nothing if real users on 4G phones sat through a 6-second LCP. PageSpeed Insights gives you both on the same call (PSI v5, free, 240 requests per minute on one API key), and CrUX alone gives you the field side plus 25-week history trends.

Three facts keep this skill honest, and they are the reason for its existence:

  • Search Console data lags by 2-3 days and drops anonymized low-volume rows from row-level exports. Quote site-wide numbers only from the totals block.
  • A sitemap entry is not an indexation verdict. The Sitemaps API reports what you submitted; the URL Inspection API reports what Google actually did with it. Saying "indexed" from a sitemap is a real, common mistake this skill refuses to repeat.
  • The Indexing API is officially restricted to JobPosting and BroadcastEvent/VideoObject pages, with a 200-publish-per-day cap. Telling a blogger "we'll batch-notify 500 article URLs" is misusing it.

The credential tiers

Tier

What you have

What it unlocks

0 - CSV

A Search Console export

search performance, quick wins

1 - API key

One free key

PageSpeed, CrUX, CrUX history

2 - Service account

SA JSON, added as property user

URL Inspection, Sitemaps, Search Analytics API

3 - GA4 + Ads

SA + GA4 property / Ads IDs

organic traffic report, Keyword Planner

State the tier before every analysis, and never claim field data when you only ran a crawler.

Main path: analyze a GSC export (no credentials)

Export: Search Console > Performance > Full report > Export > CSV. Then:

bash
python3 ~/.codex/skills/codex-seo-google/scripts/gsc_csv.py <export.csv>
python3 ~/.codex/skills/codex-seo-google/scripts/gsc_csv.py <export.csv> --json

The script detects whether the export is query-dimension or page-dimension, totals clicks and impressions with a weighted CTR and average position, splits the rows into CTR bands, and runs the quick-win detector. That detector is the whole point of performance analysis: queries sitting at position 4-10 with meaningful impressions are the highest-leverage fixes on the board, because a position gain there moves you to page 1 (positions 1-3), and every page-1 result multiplies its click share.

Install the skill

Two files. First the folder:

bash
mkdir -p ~/.codex/skills/codex-seo-google/scripts

Save this as ~/.codex/skills/codex-seo-google/SKILL.md:

markdown
---
name: codex-seo-google
description: Use when the user asks about Google's own SEO data - Search Console, GSC, PageSpeed Insights, CrUX field data, core web vitals, real CWV field data, URL inspection, indexation status, GA4 organic traffic, or search performance beyond what crawl tools show. CSV-first: a GSC export plus one python3 script gets real Google data with zero API keys.
---
# Google SEO Data

Crawl-based skills read the page. This one reads what Google itself has on
the page: field data (real Chrome user metrics), indexation status, search
performance. Everything here is free. The fastest path needs no API key at
all: export a Search Console performance CSV and analyze it locally.

## Credential tiers

| Tier | What you have | What unlocks |
|------|---------------|--------------|
| 0 - CSV | A Search Console export, any property | search performance, quick wins |
| 1 - API key | One key (2-minute GCP setup, free quota) | PageSpeed, CrUX, CrUX history |
| 2 - Service account | SA JSON with the property as its user | URL Inspection, Sitemaps, Search Analytics API |
| 3 - GA4 + Ads | SA + GA4 property / Ads IDs | organic traffic report, Keyword Planner |

State the detected tier before any analysis. Do not pretend to have field
data when you only have lab data (and never say "indexed" when you only
checked the HTML).

## Main path: analyze a GSC export (no credentials)

`Performance > Full report > Export > CSV`, then:

```bash
python3 ~/.codex/skills/codex-seo-google/scripts/gsc_csv.py <export.csv>
python3 ~/.codex/skills/codex-seo-google/scripts/gsc_csv.py <export.csv> --json
```

The script detects the dimension (query or page), totals clicks/impressions,
groups CTR, finds quick wins (position 4-10 with meaningful impressions), and
flags anything that looks like a data problem (e.g. total CTR under 0.5%).

Quick win rule from Search Analytics: queries sitting at position 4-10 with
high impressions are the highest-leverage fixes; position gains there move
to page 1 (positions 1-3).

## PageSpeed + CrUX (tier 1: API key)

```bash
curl -s "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https%3A%2F%2Fexample.com&strategy=mobile&key=$PSI_KEY" | python3 -m json.tool
curl -s "https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=$PSI_KEY" -H "Content-Type: application/json" -d '{"url":"https://example.com"}' | python3 -m json.tool
```

- PSI merges lab data (point-in-time Lighthouse) with field data (28-day
  Chrome users). CrUX only = field data, 25-week history for trends.
- CrUX 404 = not enough Chrome traffic, not an auth error. Fall back to
  PSI lab scores before blaming credentials.
- CrUX scales are string-encoded ("0.05"); parse before comparing.

## Core Web Vitals thresholds (p75 field data)

| Metric | Good | Needs improvement | Poor |
|--------|------|-------------------|------|
| LCP | <= 2.5s | 2.5 - 4.0s | > 4.0s |
| INP | <= 200ms | 200 - 500ms | > 500ms |
| CLS | <= 0.1 | 0.1 - 0.25 | > 0.25 |

- INP replaced FID on 2024-03-12. Never reference FID.
- `round_trip_time` replaced `effectiveConnectionType` in CrUX (Feb 2025).
- Always state the freshness window of the data you report.

## Search Console API (tier 2: service account)

| Endpoint | What it gives | Daily cap |
|----------|---------------|-----------|
| Search Analytics | clicks / impressions / CTR / position, 28d, dims = query, page | 30M |
| URL Inspection | real indexation verdict: PASS/FAIL, coverage, canonical, robots | 2,000/site |
| Sitemaps | submitted counts, errors, warnings | 5,000/site |
| Indexing API | notify Google of URL updates | 200 publish/day |

Key facts:

- Search Analytics has 2-3 day lag and drops anonymized low-volume rows;
  fetch the totals block separately (the CLI does) before quoting
  site-wide numbers.
- Sitemaps report *submitted* counts. URL Inspection is the indexation
  truth. Never conclude "indexed" from a sitemap entry.
- The Indexing API is officially for JobPosting and
  BroadcastEvent/VideoObject pages. State the restriction when using it.
- 403 from URL Inspection = the service account needs
  Property Owner access (Settings > Users > Add).
- Search Analytics data has 2-3 day lag; impressions/CTR/average position
  data between 2025-05-13 and 2026-04-27 was logged with a known error
  (clicks unaffected; the fix was forward-only, no backfill). Treat trends
  spanning that window with care and expect an apparent impressions drop
  after the fix.

## GA4 organic traffic (tier 3)

Sessions/users/pageviews/bounce/engagement for the Organic Search channel,
default 28 days. As of 2026-05-13 GA4 also has a native *AI Assistants*
channel (medium=ai-assistant) covering ChatGPT, Gemini, Claude, Deepseek,
Copilot and Grok. It excludes Google AI Overviews / AI Mode; Perplexity and
other sources may stay in Referral; and most AI sessions arrive referrer-less
and land in Direct, so the channel undercounts AI traffic. Read AI traffic
as a lower bound, and verify Perplexity separately.

## AI surfaces in Search Console (2026)

- Generative AI performance report (launched 2026-06-03): a dedicated view
  for AI Overviews + AI Mode visibility. **Impressions only** - no clicks,
  CTR, position or query fields. Rolling out to a subset of properties.
- AI Mode clicks and impressions also roll into the standard Performance
  totals (Web search type), so classic vs AI traffic cannot be split from
  totals alone.
- AI Mode does not have an explicit event split; treat its contributions as
  embedded in standard totals.

## Rate limits (reference)

| API | Per-minute | Per-day |
|-----|-----------|---------|
| PSI v5 | 240 QPM | 25,000 QPD |
| CrUX + History | 150 QPM shared | unlimited |
| GSC Search Analytics | 1,200 QPM/site | 30M QPD |
| GSC URL Inspection | 600 QPM | 2,000 QPD/site |
| Indexing API | 380 RPM | 200 publish/day |

## Errors

| Scenario | Action |
|----------|--------|
| No config / no credentials | use the CSV path; list tier-0 commands that work with just an export |
| CrUX 404 | insufficient Chrome traffic - fall back to PSI lab data |
| Search Analytics 403 | SA is not a user on the property; add client_email in GSC > Settings |
| Unknown dimensions in CSV | expected column names: query/page, clicks, impressions, ctr, position |
| NaN/0 total data | report NO DATA, never compute a percentage of zero |
| 429 rate limit | exponential backoff; report which API hit the limit |

## Output contract

Produce `GOOGLE-API-REPORT-{domain}.md` with: freshness note, tier used,
query/page table, quick-win list, and a limitation paragraph naming anything
that could NOT be assessed from the available credentials.

Save this as ~/.codex/skills/codex-seo-google/scripts/gsc_csv.py:

python
#!/usr/bin/env python3
"""Analyze a Google Search Console performance CSV export without any API
keys. Detects the dimension (query or page), totals clicks and impressions,
groups CTR, lists top rows, and finds quick wins: queries at position 4-10
with meaningful impressions - the highest-leverage fixes. Standard library
only. Usage:
  python3 gsc_csv.py <export.csv> [--json] [--min-impressions N]
"""
import csv
import json
import sys

MINS = {"query": "Query", "page": "Page"}


def load(path):
    rows = []
    with open(path, newline="", encoding="utf-8-sig") as f:
        r = csv.reader(f)
        header = None
        for line in r:
            if not line or all(not c.strip() for c in line):
                continue
            if header is None:
                header = [c.strip().lower() for c in line]
                continue
            rows.append(dict(zip(header, [c.strip() for c in line])))
    return header or [], rows


def fnum(v):
    if v is None:
        return 0.0
    v = v.strip().replace("%", "")
    try:
        return float(v)
    except ValueError:
        return 0.0


def main():
    args = [a for a in sys.argv[1:] if not a.startswith("--")]
    if not args:
        print("gsc_csv.py - Search Console export analyzer (no API key needed)")
        print("  python3 gsc_csv.py <export.csv> [--json] [--min-impressions N]")
        return
    flags = dict((a.split("=", 1) + ["1"])[:2] if "=" in a else (a, "1")
                 for a in sys.argv[1:] if a.startswith("--"))
    min_imp = int(flags.get("--min-impressions", 1000))

    header, rows = load(args[0])
    dims_found = [k for k in MINS if k in header]
    if not dims_found:
        print("DATA PROBLEM: no query or page column in CSV header")
        print("expected columns from Search Console export: "
              "query/page, clicks, impressions, ctr, position")
        return
    dim = dims_found[0]
    need = [k in header for k in ("clicks", "impressions", "ctr", "position")]
    if not all(need):
        print("DATA PROBLEM: missing columns %s" %
              [k for k in ("clicks", "impressions", "ctr", "position")
               if k not in header])
        return

    parsed = []
    for row in rows:
        if not row.get(dim):
            continue
        parsed.append({
            "label": row[dim],
            "clicks": int(fnum(row.get("clicks"))),
            "impressions": int(fnum(row.get("impressions"))),
            "ctr": fnum(row.get("ctr")),
            "position": fnum(row.get("position")),
        })

    if not parsed:
        print("NO DATA: 0 rows parsed (the export may be empty)")
        return

    tot_clicks = sum(r["clicks"] for r in parsed)
    tot_imp = sum(r["impressions"] for r in parsed)
    wctr = (tot_clicks / tot_imp) if tot_imp else 0.0
    wpos = sum(r["position"] * r["impressions"] for r in parsed) / max(1, tot_imp)

    wins = [r for r in parsed
            if 4.0 <= r["position"] <= 10.0 and r["impressions"] >= min_imp]
    wins.sort(key=lambda r: -r["impressions"])

    buckets = {"under_1": 0, "1_5": 0, "5_10": 0, "over_10": 0}
    for r in parsed:
        c = r["ctr"] * 100
        if c < 1:
            buckets["under_1"] += 1
        elif c < 5:
            buckets["1_5"] += 1
        elif c < 10:
            buckets["5_10"] += 1
        else:
            buckets["over_10"] += 1

    top = sorted(parsed, key=lambda r: -r["clicks"])[:10]
    out = {"file": args[0], "dimension": dim, "rows": len(parsed),
           "totals": {"clicks": tot_clicks, "impressions": tot_imp,
                      "ctr": round(wctr, 4), "position": round(wpos, 2)},
           "top_rows": [{"label": r["label"], "clicks": r["clicks"],
                         "impressions": r["impressions"],
                         "ctr": round(r["ctr"], 4),
                         "position": round(r["position"], 1)} for r in top],
           "quick_wins": [{"label": r["label"], "position": round(r["position"], 1),
                           "impressions": r["impressions"],
                           "clicks": r["clicks"]} for r in wins[:10]],
           "ctr_buckets": buckets}
    if "--json" in sys.argv:
        print(json.dumps(out, indent=2))
        return

    print("GSC export scan %s" % args[0])
    print("  dimension: %s   rows: %d" % (dim, len(parsed)))
    print("  totals: %d clicks / %d impressions / CTR %.2f%% / avg pos %.1f"
          % (tot_clicks, tot_imp, wctr * 100, wpos))
    if wctr < 0.005 and tot_imp:
        print("  FLAG: total CTR under 0.5% - check whether this is a "
              "brand-heavy or impression-broad export")
    print("  top rows by clicks:")
    for r in top:
        print("    %-42s %6dc %7di %5.2f%% pos %5.1f"
              % (r["label"][:42], r["clicks"], r["impressions"],
                 r["ctr"] * 100, r["position"]))
    print("  quick wins (pos 4-10 && impressions>=%d):" % min_imp)
    if wins:
        for w in wins[:10]:
            print("    %-42s pos %5.1f %7di %6dc"
                  % (w["label"][:42], w["position"], w["impressions"],
                     w["clicks"]))
    else:
        print("    none")
    print("  ctr buckets: <1%% %d | 1-5%% %d | 5-10%% %d | >10%% %d"
          % (buckets["under_1"], buckets["1_5"], buckets["5_10"],
             buckets["over_10"]))


if __name__ == "__main__":
    main()

Run

bash
python3 ~/.codex/skills/codex-seo-google/scripts/gsc_csv.py /path/to/export.csv
python3 ~/.codex/skills/codex-seo-google/scripts/gsc_csv.py /path/to/export.csv --json

A real run: the query-dimension export

I analyzed an export shaped exactly like the Search Console full report - same column order (Query, Clicks, Impressions, CTR, Position), same number formats, 15 rows covering brand terms, broad terms, and the typical position spread. The values themselves are a constructed sample for this walkthrough; the analysis is what matters, and it runs unchanged on your own export. Literal output:

text
GSC export scan /tmp/gsc-export.csv
  dimension: query   rows: 15
  totals: 1028 clicks / 29330 impressions / CTR 3.50% / avg pos 4.7
  top rows by clicks:
    auspia ai search                              620c     860i 72.09% pos   1.2
    auspia tool                                   210c     290i 72.41% pos   1.1
    what is geo marketing                          80c   12400i  0.65% pos   1.4
    is google search dying                         18c    3400i  0.53% pos  11.2
    codex claude seo skills                        15c     640i  2.34% pos   3.6
    google ai overviews still seo                  14c    1980i  0.71% pos   6.2
    free ai search beyond google                   12c    2140i  0.56% pos   7.4
    seo automation for small teams                 11c     760i  1.45% pos   3.1
    best no-google search engine                    9c    1890i  0.48% pos   8.1
    ai overview citation gap                        9c     710i  1.27% pos   4.2
  quick wins (pos 4-10 && impressions>=1000):
    free ai search beyond google               pos   7.4    2140i     12c
    google ai overviews still seo              pos   6.2    1980i     14c
    best no-google search engine               pos   8.1    1890i      9c
    ai search landscape 2026                   pos   5.9    1500i      7c
  ctr buckets: <1% 9 | 1-5% 4 | 5-10% 0 | >10% 2

Read the output like a diagnosis:

  • The totals line gives you the point of view. On this export, 3.50% CTR and an average position of 4.7 - and the averages hide the real structure, which is why the CTR buckets exist: 9 of 15 rows sit under 1% (the impression-broad tail), 2 rows sit over 10% (the brand terms). That split is the shape of the whole account, and it takes one glance.
  • The quick wins list is the actionable part. Four queries sit at position 4-10 with 1,500+ impressions each: "free ai search beyond google" at 7.4, "google ai overviews still seo" at 6.2, "best no-google search engine" at 8.1, "ai search landscape 2026" at 5.9. These are the pages to improve this month - small position gains there produce click changes of a much larger magnitude than the same gains on shorter-tail terms.
  • The trend rows tell you what not to prioritize. "is google search dying" sits at 11.2 with 3,400 impressions. Position 11 is one step from page 2, and its CTR is 0.53% - a real risk, but a lower-leverage risk than the 4-10 band, because a small position gain does nothing for you there and a large one is harder.
  • Brand terms are the shadow anchor. "auspia ai search" and "auspia tool" at 72%+ CTR are the reason the export is healthy at a glance. When you analyze your own export, do not let brand rows mask the real competitive picture.

A real run: page-dimension export and the failure paths

The same script on a page-dimension export (columns Page, Clicks, Impressions, CTR, Position) switches dimension detection automatically:

text
GSC export scan /tmp/gsc-export-pages.csv
  dimension: page   rows: 12
  totals: 117 clicks / 7790 impressions / CTR 1.50% / avg pos 5.3
  top rows by clicks:
    https://auspia.ai/blog/google-geo-aeo-stil     22c    1140i  1.93% pos   3.8
    https://auspia.ai/blog/ai-video-generator      16c     980i  1.63% pos   4.9
    https://auspia.ai/blog/when-customers-ask-     14c     880i  1.59% pos   4.1
    https://auspia.ai/blog/trendsnap               12c     890i  1.35% pos   2.7
    https://auspia.ai/blog/how-to-recover-from     11c     760i  1.45% pos   5.7
    https://auspia.ai/blog/codex-seo-schema         9c     480i  1.88% pos   5.4
    https://auspia.ai/blog/ai-search-citation-      8c     530i  1.51% pos   7.0
    https://auspia.ai/blog/codex-seo-backlinks      7c     590i  1.19% pos   6.3
    https://auspia.ai/blog/codex-seo-technical      6c     470i  1.28% pos   6.8
    https://auspia.ai/blog/how-to-fix-keyword-      5c     410i  1.22% pos   7.6

And then there are the two failure paths that make the script trustworthy. Feed it a CSV that is not a Search Console export and it refuses to score:

text
DATA PROBLEM: no query or page column in CSV header
expected columns from Search Console export: query/page, clicks, impressions, ctr, position

Feed it a valid-header export with zero data rows and it says so directly:

text
NO DATA: 0 rows parsed (the export may be empty)

Neither of those paths prints a score. A number computed from the wrong columns or an empty table is worse than no number, and the script treats it as such - the same honesty gate as the backlinks article in this series.

The reference tables

The tables in the SKILL.md are worth keeping open while you work. The one that decides most repair work is the Core Web Vitals thresholds table - but with one rule above all: compare like with like. Lab tests give you a point-in-time number; CrUX gives you the p75 of real users; only the same kind of data can be compared to the same thresholds, and every report must state which kind it used.

The second table decides how the data gets collected: the rate limits. Two numbers matter most in practice - the 200 URLs/day cap on Indexing API publishes (spend them on what you actually update, not on everything) and the 2,000/site daily cap on URL Inspection (batch it, don't loop it).

The third table is the credential ladder, and it is the honest version of what people think these tools need: the most useful feature - performance analysis - needs no credentials at all.

What changed for AI surfaces in 2026

The 2026 changes are the reason this skill carries a dedicated section. Three things, all official and all subtle:

  • The Generative AI performance report (launched June 3, 2026) is the first dedicated view of AI Overviews + AI Mode visibility. It is impressions-only - no clicks, CTR, position, or query dimensions - and it is rolling out to a subset of properties. If it is available to you, it is the closest thing to "AI visibility" Google publishes; if it is not, the data is simply not there yet.
  • AI Mode traffic rolls into your standard Performance totals under Web search. Clicks from external AI Mode links are counted as clicks. That means you cannot cleanly split classic vs AI traffic from a standard export - the numbers are entangled by design.
  • GA4 grew a native AI Assistants channel (May 13, 2026) for ChatGPT, Gemini, Claude, Deepseek, Copilot, and Grok referrals. It excludes Google AI Overviews and AI Mode, it may not catch Perplexity, and most AI sessions arrive referrer-less and land in Direct anyway. Read the channel as a lower bound on AI traffic, not a measurement of it.

Add the reliability footnote from the same year: impressions, CTR, and average position data between 2025-05-13 and 2026-04-27 was affected by a known logging error (clicks were fine; the fix was forward-only with no backfill). If a trend line crosses that window, treat it with care - and expect an apparent impressions drop after the fix that is not a real traffic loss.

Troubleshooting table

What you see

Meaning

Action

DATA PROBLEM: no query or page column

Not a GSC export, or wrong sheet

Re-export from Search Console > Performance > Full report; never paste tables from docs

NO DATA: 0 rows parsed

Empty export (date range with no traffic, or filtered to nothing)

Widen the date range; check the property is the active one

totals: CTR 0.62% with 6 figures

Brand-heavy or impression-broad export

Read CTR buckets and quick wins, not the headline total

CrUX 404

Not enough Chrome traffic for field data

Use PSI lab scores; state the limitation

403 on a Search Analytics call

Service account is not a user on the property

Add client_email under GSC > Settings > Users > Add

Impression trend drop around 2026-04

The known logging fix landed

Cross-check against clicks (unaffected); do not panic

Queries you know exist are missing

Anonymized rows are dropped from row-level exports

Use the totals block; split into narrower filters

Install this skill by pasting to Codex

Copy this paragraph into Codex together with the two code blocks above:

Read the two code blocks in the current message. Create ~/.codex/skills/codex-seo-google/SKILL.md (markdown block) and ~/.codex/skills/codex-seo-google/scripts/gsc_csv.py (python block) exactly as written. Then run python3 ~/.codex/skills/codex-seo-google/scripts/gsc_csv.py --help style check via running the script with no arguments, tell me the credential tier I need for my use case, and list the steps to export my Search Console performance CSV.

FAQ

Do I need a Google Cloud project for the main workflow? No. The CSV path uses nothing but a file you can download from Search Console. A Google Cloud project API key is only needed for PageSpeed and CrUX calls (tier 1), and a service account only if you want the Search Analytics and URL Inspection APIs (tier 2). The skill's tier table exists to make that distinction clear up front.

Why does my CSV miss some queries I know I get traffic for? Row-level Search Analytics exports drop anonymized low-volume aggregates. This is by design at Google's end. Fetch the totals block separately - that is why the skill's CLI treats the totals query as its own call - and read the quick wins list as "visible rows", never as the whole truth.

What is the difference between lab data and field data again? Lab is Lighthouse on a controlled test - fast, repeatable, and exactly one snapshot. Field is the p75 of real Chrome users over 28 days - slower to get, and the only data Google actually uses for CWV thresholds. When a lab audit says something passes and CrUX says it fails, CrUX wins.

Can this skill submit URLs to Google? Not as its own feature - the Indexing API is restricted to JobPosting and BroadcastEvent/VideoObject pages, with a 200/day cap, and using it on a blog would be misuse of the API. For normal articles, the honest paths are sitemaps, regular performance monitoring, and Google's own URL inspection tool.

I saw impressions drop after mid-2026. Did I lose traffic? Probably not. The known indexing-logging error affected impressions, CTR, and average position between May 2025 and April 2026; the fix was forward-only with no backfill, so the numbers correct themselves over time. Compare clicks across the window - they were never affected.

Is there anything this skill cannot do? It cannot invent credentials, and it will not pretend to. Without a service account you cannot read indexation verdicts or the real-time Search Analytics API, so the skill's output contract requires a limitation paragraph naming exactly what could not be assessed. That paragraph is part of the deliverable, not a footnote to it.

Next in the series (post 12 of 20): [How to Set Up Codex for Topic Clustering (Full SKILL.md Included)](https://auspia.ai/blog/codex-seo-cluster) - serp-overlap clustering and content architecture.

Previous in the series: How to Set Up Codex for Local SEO Audits (Full SKILL.md Included). The full series roadmap lists all 20 posts.

Author: Olivia Stone, SERP Intelligence Researcher Across 25k+ Queries at Auspia. Olivia writes about search performance data, ranking patterns, and what the numbers actually mean.

Based on the open-source claude-seo project by AgriciDaniel (MIT license, GitHub). This series adapts it for the Codex runtime: rewritten methodology, a Codex-native SKILL.md, and new evidence collectors written from scratch for this series.

Explore this topic

Keep following the same growth thread