A poisoned Google SERP comes back as a 200 with a full organic list, an AI Overview and People Also Ask, and the wrong sites in the organic list. There’s nothing in the response that marks it. A blocked request makes a client retry, while a complete page with the wrong top ten goes into the database as a ranking change.
The damage shows up a step later. A rank tracker fed that page reports a site gone from the top 100 while the owner’s own search shows it at position 4. By Wednesday the Monday alert has become a title-tag rewrite, and nobody can say afterwards whether the next move came from Google or from the edit. Technical SEO News advised in October to keep tracker charts out of client reports unless a Search Console figure went next to them.
Rank trackers documented the problem from mid-September, and I measured it twice. The first run, the cross-API pool, read 4,834 Google result pages for 203 US queries through 11 SERP APIs and two Chrome windows. The reads fell on five days between October 15 and November 7, and I compared every page with what the other APIs returned for the same query on the same day.
The second run, the 11-country run, applied the rule the trackers count with, the disappearance of a query’s usual sites. It covered 24,200 pages for 1,120 queries in 11 countries, read through one API between October 29 and November 4. The first run shows how often an API returns such a page, and the second shows what the counting rule flags along the way.
What happened since September 13
Requests to Google started failing in large numbers on September 13, and from September 20 the failures turned into complete pages with the usual sites swapped for others. Google serves some automated requests a full page with the wrong results, and the trackers that compare each page with the query’s history were the first to put a number on it.
Nozzle flags a page when the sites it usually holds for a query disappear. Its open data puts that share at 0.3% of pages before September 14 and 4% on September 20. On September 29 it was 23% of page-one reads and 38% of queries on page one or two, and on September 30 it reached 44%. AccuRanker saw 30.2% of its desktop fetches fail its own check on September 25.
The September spam update ran from September 24 to October 8. By October 15 the share was down to 4% of queries at Nozzle, and AccuRanker reported 0.28% on October 20.
Then it rose again. Nozzle’s page-one share went from 1.3% on October 15 to 20% on October 21, the last day in its open data, and the company reported 34.8% on October 27. Page two was flagged about 1.5 times as often as page one, usually while page one of the same query passed.

The green bands are the days of my two measurements. The cross-API pool fell on October 15, the quiet day between the two waves, on October 29, when one API and one Chrome window read the queries and no page got a label, and on November 5 to 7, after the open data ends. The 11-country run, baseline included, went from October 26 to November 4, inside the second rise.
The pool shows the same shape as the chart. A page gets a label when at least three other APIs read the same query that day and agreed with each other. Of the labelled pages, 4.7% disagreed with that consensus on October 15, 12.4% on November 5, 13.7% on November 6 and 4.9% on November 7.
What a poisoned page looks like
On November 5 at 14:38 UTC I sent how many calories in a banana to one of the APIs ten times, 15 seconds apart, and loaded the query in a Chrome window in the same second as the first call. Calls one to nine returned the page that three other APIs had agreed on that morning, with Harvard’s Nutrition Source, Healthline, Chiquita, Dole, Eat This Much and the USDA.
Call ten returned umd.edu, WebMD, alma.food, ro.co, rochester.edu, nutriscan.app, Thrillist, inkin.com and Quora. The Chrome window had that second page from the start, with EatAI at position six where call ten had NutriScan. The same window had shown the Harvard page at 08:39.

Both pages were 200s with the same blocks. That API had 863 labelled reads in the pool, first reads and re-reads together, and 21 of them were off, with under 30% of their domains in the other APIs’ consensus. The off pages had an AI Overview in 95.2% of cases, the same share as the rest. They had a median of nine organic results against eight, four People Also Ask questions and eight related searches on both sides, and a median response of 33,017 bytes against 33,192.
Every title and snippet on 16 of the 21 off pages carried the query’s words, and the lowest share on any of them was 78%. The organic list is the only field that differs, and it differs only against another read of the same query.
The video runs three scripts on the stored bodies of those ten calls. The first prints the status and the nine hosts of call ten, the second compares them with call nine and raises when under 30% match, and the third runs the same rule over 200 queries and re-reads the ones it flags.
Each script reads the JSON the API returned, so the same check runs inside any pipeline that keeps its responses.
How often each SERP API returned a poisoned page
The cross-API pool shows which provider returns such a page and how often. Across 1,771 labelled first reads from 11 SERP APIs between October 15 and November 7, 6.7% were off the other APIs’ consensus, a complete 200 whose top ten disagreed with the other APIs. The share per API ran from 0.6% to 28.7%.
The table counts each API’s first read of a query on a day, before any re-read. A read is off when under 30% of the domains it returned were in the other APIs’ consensus for that query and day, and rows are sorted by that share. The last column is November 7, the only day all 203 queries were read, with 1,083 of the 1,771 labelled reads. A blank cell means the API had no labelled reads that day.
| SERP API | Labelled days | Off of labelled reads | Share off | November 7 |
|---|---|---|---|---|
| SERPHouse | 1 | 1 of 177 | 0.6% | 1 of 177 (0.6%) |
| Oxylabs | 4 | 2 of 260 | 0.8% | 2 of 153 (1.3%) |
| Serper | 4 | 3 of 290 | 1.0% | 3 of 182 (1.6%) |
| ScrapingBee | 1 | 1 of 77 | 1.3% | 1 of 77 (1.3%) |
| HasData | 4 | 4 of 291 | 1.4% | 2 of 178 (1.1%) |
| Cloro | 1 | 4 of 120 | 3.3% | 4 of 120 (3.3%) |
| ScraperAPI | 1 | 1 of 28 | 3.6% | |
| Bright Data | 1 | 3 of 40 | 7.5% | |
| SerpApi | 1 | 5 of 44 | 11.4% | |
| Apify | 4 | 62 of 329 | 18.8% | 40 of 196 (20.4%) |
| SearchApi | 3 | 33 of 115 | 28.7% | |
| All 11 APIs | 119 of 1,771 | 6.7% | 53 of 1,083 (4.9%) |
HasData was at 1.4% (4 of 291) and at 1.1% on November 7. Every API returned at least one off page, and all 119 were 200s with organic results. The mix of days and queries doesn’t explain the spread, since on the query-days each API’s labelled reads covered, 2.5% to 4.4% of the other APIs’ first reads were off. On November 7 alone the pooled share was 4.9%.

Within one API the share also moved by day. The API at 18.8% went from 5.3% of its first reads on October 15 to 34.2% on November 5 and 20.4% on November 7, while the API at 0.8% overall returned no off page on three of its four days.
What a browser spot-check settles
My two Chrome windows, a clean profile and a signed-in one on the same US connection, shared a median of 79% of their top ten on 66 pairs of pages for 50 queries. The low end was 24% for rank tracker api on October 15, 39 seconds apart.
The usual spot-check is a search in an incognito window, and a PSA thread on r/SEO argues that it can’t validate a tracker at all. The pairs back that up. The clean window was off against the APIs’ consensus on 20 of its 129 labelled reads (15.5%), 15 of them on November 6 alone, and the signed-in window on 1 of 55. A browser is one more read of the same query. When it disagrees with the API, the disagreement alone doesn’t say which page Google shows to people.

Four pairs shared under half of their top ten, and 11 shared 90% or more, so the comparison has to be against something more stable than one more window.
What a history check and a re-read catch
The common defence among trackers is to compare a new page with the keyword’s recent history and fetch it again when the familiar sites are gone. The cross-API pool has 670 labelled reads with at least one earlier day stored for the same API and query, from 5 APIs and 195 queries. A flag at under 30% of domains in common with the stored days caught 37 of the 53 off reads among them and flagged 34 pages that matched the consensus.
How many stored days the check uses changes how many pages it flags. With one earlier day (480 reads), the check flagged 60 pages, 27 of them among the 41 off reads. With two days (139 reads) it flagged 11 pages, 10 of them the 10 off reads. With three or more days (51 reads) the stored domains covered enough of the two off reads that neither was flagged, so the union of old pages needs a cap on its age.
A re-read works without any history. On November 5 and 6 I sent one API ten calls, 15 seconds apart, for each of 200 queries, every query on one of the two days. A call counts as off when under 30% of its domains are among those at least half of the other nine calls share.
That gave 85 off calls on 44 query-days, and for 58 of them the call 15 seconds away was back on the shared domains. The first call was off for 8 of the 200 queries. Re-reading every new query once, keeping the first page when the two share 30% or more of their domains and letting a third read decide by majority otherwise, left 4 off first reads in 200 at about 1.05 extra calls per query.

On 27 of the 44 query-days a single call was off, one query-day had all ten calls off, and the off calls fell on every position, from 6 in call two to 13 in call nine, so a pipeline can’t pick a safe call and has to compare two.
The usual-sites rule on 24,200 pages in 11 countries
The pool, the history check and the ten calls all compare one read of a query with another read of it. A tracker reads each keyword through one source, so it counts with one rule, the disappearance of a query’s usual sites. That’s the history check with a baseline in place of stored days, and I ran it on pages of my own.
The 1,120 queries cover 11 countries, 212 of them in the US and 90 to 92 in each of Australia, Canada, France, Germany, India, Italy, Mexico, the Netherlands, Spain and the UK. I read desktop pages one and two four times between October 26 and 28 and took as the query’s usual sites the domains present on at least 60% of those reads. Then I read the same pages in 11 runs between October 29 and November 4, 24,200 pages in all, and flagged a page when under half of its usual sites were on it.
A second, stricter count kept only the flagged pages where under half of the results also carried a word of the query in the title or snippet. That’s the page a reader would call wrong.
The queries split into 576 generic, 312 local, 144 near-me and 88 brand queries across 12 industries, from real estate to jobs.
| Query type | Page | Pages | Flagged by the rule | Flagged and off topic |
|---|---|---|---|---|
| Generic | 1 | 6,247 | 264 (4.2%) | 0 |
| Generic | 2 | 6,333 | 876 (13.8%) | 7 (0.1%) |
| Local | 1 | 3,420 | 236 (6.9%) | 0 |
| Local | 2 | 3,410 | 489 (14.3%) | 0 |
| Near me | 1 | 1,529 | 100 (6.5%) | 4 (0.3%) |
| Near me | 2 | 1,535 | 257 (16.7%) | 10 (0.7%) |
| Brand | 1 | 893 | 19 (2.1%) | 1 (0.1%) |
| Brand | 2 | 833 | 96 (11.5%) | 17 (2.0%) |
| All | 24,200 | 2,337 (9.7%) | 39 (0.2%) |
The rule flagged 9.7% of the pages, 5.1% on page one and 14.2% on page two, and the share by run moved between 7.1% and 14.0%. By country it ran from 7.7% in the US to 12.0% in Germany. The strict count came to 39 pages, 34 of them on page two, 14 in Mexico, 10 in the UK and 1 in the US. Brand queries kept their own domain in the top three on 878 of 893 page-one reads (98.3%).
The 2,298 pages between the two counts are pages the rule flagged and a reader wouldn’t question.
What the rule flags besides poisoned pages
The baseline pages themselves show how much of the 9.7% is the rule. When each of the four baseline runs is judged against the other three, under half of the usual sites are on the page on 2.7% to 21.9% of page-one reads and 4.5% to 26.4% of page-two reads. The measurement runs came in at 2.8% to 10.0% on page one and 11.3% to 18.0% on page two. Pages read before the measurement and used to define the usual sites get flagged as often as the pages measured against them.

The flagged pages are also on topic about as often as the rest. Every result’s title or snippet carried a word of the query on 84.4% of flagged pages and on 87.7% of the others, and at least half of the results did on 98.3% and 99.4%. Social and forum sites held 9.1% of the organic results on flagged pages and 9.5% on the rest.
The flags also clear within days. Of the 2,205 query-pages with usual sites, 1,084 were never flagged in the 11 runs, 570 were flagged once, 257 twice and 4 on all 11. Of the flagged pages with a later read within three days, 1,898 of 2,174 (87.3%) were back on their usual sites by then.
The 39 strict pages, where the usual sites were gone and under half of the results carried a query word, split by their domains. On 26 of them the domains answer the query in a local vocabulary the word check doesn’t know. In the UK, lawyer near me returned solicitors and fiber internet returned providers who spell it fibre. In Australia moving companies near me returned removalists, and in Mexico alquiler de pisos returned idealista, lamudi and homie, none of which repeat the query’s words in a title.
Eleven more were brand queries, ten of them on page two, where Google filled the page with the brand’s social profiles and news about it. Nine of those were for one Mexican property portal. Two were pages with a single agency on them.
The banana page, a full top ten for a different query, has no counterpart among the 39. On these 24,200 pages the rule measured how much Google’s results move between reads.
What this means for a SERP pipeline
Store the raw page with its timestamp. The status code was 200 on every off page in both pools, and only a second page of the same query shows the difference. Compare each new top ten with the stored days for the same query, and re-read a page with under 30% of its domains in common within a minute, before it goes into a report. Fifteen seconds was enough to put 58 of 85 off calls back on the shared domains.
Add a page to the history only after a second read agrees with it. Of the 34 pages the history check flagged by mistake, 12 were judged against a history whose only entry was an off first read stored as is. Keep two days of history, the depth that flagged 11 pages to catch all 10 off reads. One day flagged 60 for 27, and the four-run baseline of the 11-country run flagged a tenth of all pages. For a query with no history, re-read it once.
Give page two its own history. In the 11-country run its usual sites changed between ordinary reads far more than page one’s, and one threshold for both pages flagged page two nearly three times as often. Run the word check with the local vocabulary, so solicitors count for lawyers and fibre for fiber. For a brand query, check whether the brand’s own domain is in the top three, where it was on 98.3% of page-one reads.
A tracker fed from such a pipeline reports a drop only when two reads agree on it, and a client call about a vanished keyword starts with the two stored pages.
The check itself reads two stored responses and compares the domains of their organic results. The last two labels of the host are enough for US pages, and a public suffix list covers .co.uk and the other two-level suffixes.
import json
from urllib.parse import urlparse
def domains(path):
page = json.load(open(path, encoding="utf-8"))
hosts = [urlparse(r["link"]).hostname.removeprefix("www.") for r in page["organicResults"]]
return [".".join(h.split(".")[-2:]) for h in hosts]
def reread_needed(new_path, stored_path, threshold=0.3):
new, stored = domains(new_path), set(domains(stored_path))
shared = sum(d in stored for d in new)
print(f"{shared} of {len(new)} domains in common, {'re-read' if shared / len(new) < threshold else 'keep'}")
return shared / len(new) < threshold
reread_needed("banana-call-10.json", "banana-call-09.json")On calls ten and nine of the banana query it prints the share and the decision.
0 of 9 domains in common, re-readCall nine against call eight shares 6 of 7 domains and keeps the page, since the two differ by one site.
The Google SERP API returns the organic results and the AI Overview as JSON, so the domain comparison runs on the link of each organicResults entry with no HTML parsing.
How I tested
The cross-API pool holds 4,834 reads of 203 US queries on October 15 and 29 and November 5, 6 and 7, through 11 SERP APIs and two Chrome windows on a US connection. Of the queries, 77 came from Search Console for hasdata.com, 76 from MS MARCO and 50 I chose myself. Each API was asked for US results with no city named.
A read gets a label when at least three other APIs read the same query on the same day and their top tens share a median of 50% or more of their domains pairwise. The consensus is the set of domains at least half of those reads hold, and a read is off when under 30% of the domains it returned are in it, so a page with nine results is judged on nine. That left 1,771 of the 2,354 API first reads labelled.
Domains are compared as registrable names, with www. and other subdomains folded in, so Harvard’s Nutrition Source counts as harvard.edu. The Chrome windows never count toward the consensus.
The vendor table uses each API’s first read of a query on a day. The history check compares a read with the same API’s earlier stored days for the query at the same 30% threshold, and the ten-call run scored each call against the other nine calls of its own run.
The 11-country run was read through the HasData Google SERP API as desktop pages one and two for 1,120 queries, 1,117 of which had enough baseline reads to judge. The baseline is four runs between October 26 and 28, and a query’s usual sites are the domains on at least 60% of its baseline pages. The 11 measurement runs between October 29 and November 4 gave 24,200 judged pages. A page is flagged when under half of its usual sites are on it, and counted as off topic when under half of its results also carry a word of the query in the title or snippet.
One baseline run on October 27 is excluded. In all 11 countries about half of its sites differed from every other baseline run, on topic but from another region, with New York’s mortgage calculator answered by Missouri and Illinois banks. One measurement day, October 30, is also excluded. That day Google started wrapping result links in google.com/goto redirects, most links in my run came back wrapped, and domains couldn’t be matched.


