Automating part of the audit
Data extraction, AI, and what has to stay human.

Automate what you can verify by running it again, let AI draft what you can spot-check, and keep the decisions for yourself. Exports and hygiene checks are machine work. The verdict on where the growth is, which is what an audit is paid for, stays with a person who will have to defend it in a room.
For this chapter, say a B2B SaaS at 2M ARR with 400 blog articles, GA4 linked to BigQuery, Search Console, a CRM and a billing tool.
Sort every audit task by what a mistake costs
Go through your audit plan line by line and assign a mode. A wrong export is caught by reconciling totals, while a wrong priority costs six months of the team’s work and nobody catches it.
| Task | Mode | Control |
|---|---|---|
| Exports from analytics, Search Console, CRM, billing | Script | Row counts and totals match the source interface |
| Crawl: status codes, titles, canonicals, hreflang | Script | A sample of URLs opened by hand |
| UTM hygiene, CTR anomalies | SQL | Review of the flagged list |
| Labeling content by funnel stage | AI, then human sample | Agreement with a hand-labeled sample |
| Grouping churn reasons, tickets, call transcripts | AI, then human | Each theme carries quotes you can open |
| First draft of a competitor cold read | AI draft, human rewrite | You visit the site yourself |
| Verdict, scope, priorities | Human | Nothing to delegate |
Build one extraction layer you can run twice
The value of scripting an export shows up the second time you run it: a month later, after the fixes, on the same definitions. Keep every extraction in a folder named by date, store the raw files untouched, and write down the date range and filters next to them.
| Source | How to extract | Watch out for |
|---|---|---|
| GA4 | BigQuery export or the Data API | The export has no history before you link it, so link it on day one |
| Search Console | Search Analytics API | Paginate past 25,000 rows. Some queries are anonymized, so totals by query fall short of totals by page |
| Site | Crawler in headless mode | Crawl by template, as in the SEO audits chapter |
| CRM and billing | API or scheduled export | Align account identifiers before joining anything |
Reconcile before analyzing. If the total in your table differs from the interface, find out why and write the reason down, since the client will spot the difference in the first meeting.
Script the two checks that pay off first
UTM hygiene is the classic one, because a source spelled three ways becomes three channels in every report. On the GA4 export:
SELECT
LOWER(collected_traffic_source.manual_source) AS normalized_source,
COUNT(DISTINCT collected_traffic_source.manual_source) AS spellings,
STRING_AGG(DISTINCT collected_traffic_source.manual_source) AS variants,
COUNT(*) AS events
FROM `project.analytics_123456.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260101' AND '20260630'
AND collected_traffic_source.manual_source IS NOT NULL
GROUP BY normalized_source
HAVING COUNT(DISTINCT collected_traffic_source.manual_source) > 1
ORDER BY events DESC;
The rest of the tracking review, including missing UTMs on blog CTAs and menus, is in the data reliability chapter.
The second check compares each page’s CTR with the site’s own CTR at the same position. An external CTR curve says little about your SERPs, and this version needs none.
WITH by_position AS (
SELECT ROUND(position) AS pos,
SUM(clicks)::float / NULLIF(SUM(impressions), 0) AS site_ctr
FROM gsc_pages
GROUP BY 1
)
SELECT g.page, g.impressions, g.position,
g.clicks::float / g.impressions AS page_ctr,
b.site_ctr
FROM gsc_pages g
JOIN by_position b ON ROUND(g.position) = b.pos
WHERE g.impressions >= 1000
AND g.clicks::float / g.impressions < b.site_ctr * 0.5
ORDER BY g.impressions DESC;
The thresholds are starting points. Adjust them to the size of the site, and read the flagged pages yourself before calling them a finding.
Use AI where you can check the output
Content labeling is a good first use. On our example, the editorial calendar says which of the 400 articles are TOFU, MOFU or BOFU, and those labels are often optimistic. Give the model fixed definitions, force it to quote the page, and let it say it isn’t sure.
You label one page of a B2B SaaS website.
Product: {product_summary}
Buyer: {icp_summary}
Labels:
TOFU: teaches a topic, useful even to someone who never buys this kind of product.
MOFU: helps someone compare approaches or build a case for this kind of product.
BOFU: helps someone choose or buy this product.
Return JSON only:
{"label": "", "evidence": "exact quote from the page", "main_cta_url": "", "unsure": false}
Use only the page below. If the page does not support a label, set unsure to true.
Page:
{page_text}
Before running it on all 400, label 40 articles by hand without looking at the model’s answers, then compare. Where you disagree, either your definitions are loose or the model is wrong, and you fix the prompt or your own label. Only then run the full set and join it with the declared labels. A finding looks like this: “Most articles the calendar labels MOFU teach a general topic, and their main CTA points to another article.”
The same pattern works for grouping churn reasons from tickets, or call transcripts into objections: fixed categories, a mandatory quote, a sample read by a human. For a competitor cold read, let AI draft a description of the menu, hero and resources, then visit the site yourself and rewrite it. The first impression of a real visitor is the reason you do this exercise.
AI can also turn crawl output into a first draft of your technical sheet, with each item written as problem, consequence, action. You check each line against the crawl.
The rules that keep the deliverable honest
- AI never produces a number in the deliverable. Every figure traces back to a query or an export, listed in an appendix.
- Every qualitative claim carries its source: a URL, a quote, a call.
- A recommendation that would hold for any SaaS gets deleted.
- Client data has rules. Check what the contract and the tool’s terms allow, and strip names and emails from exports before they reach a model.
Generated output also needs a filter of its own. On the Deux.io project, AI helped produce page variations, and every output went through an AI content checker and a duplicate content checker. An audit needs the same mindset, with a human as the checker.
What has to stay human
The opening verdict comes first on this list: what is solid, where the real lever is, what you won’t look at and where you aren’t the best placed to recommend. A model can summarize a dashboard, and it has no stake in being right.
Then the choices that depend on context. Picking three competitors for a stated reason. Giving each resource type a score out of ten. Deciding that a page goes in the trash. Sitting with customers and hearing how they describe the problem. The confidence score in your prioritization, which is a judgment by definition. Then the presentation, where you bring a team along on conclusions it may not like.
Once the audit is delivered, the scripts become its monitoring. Run the same queries each month, compare with the previous folder and review only what moved. That is close to how we run recurring work at Derrick: AI agents handle much of it, under written rules, with human review of what matters.
Your next step: take the audit plan you have, mark each line script, AI or human, and script the two exports you dread most. Then hand-label 40 pages before you let any model near the other 360.