Submitted-and-indexed went from 164 to 247 on the same 524 URLs. That is 38.8% → 58.5% in fifteen days, the best number this fleet has produced all year.

Then I grouped the same rows by site. Nineteen of 45 sites moved up, 24 did not move at all, and two went down. The five biggest movers produced 43 of the 85 newly indexed URLs — 50.6% of the gain — while eight sites still had zero of ten sampled URLs indexed.

The aggregate was real. It was also a story about five sites that the other forty were invited to.

This post covers what I measured, the two hypotheses that failed, and the instrumentation bug that had been hiding a fifth of the network from every report I ran.

The sample, and what it is not

Forty-five sites, ten sitemap URLs each, inspected through the Search Console URL Inspection API on 2026-10-15 and again on 2026-10-30. Same 524 URLs both times — intersection 524, zero added, zero dropped. A fixed panel, so the movement below is genuine URL-level state change rather than sampling churn.

What it is not: a census. The fleet serves 67,543 HTML pages across 119 sites, so 524 URLs is 0.8% of it, and the panel is capped at ten per site on sites with a median of 243 pages. Every number after this point is a sample statistic. I keep saying that because the last time I forgot, I shipped a slide that said "1.5% redirect rate" about two broken domains.

The pages in the panel are mostly long-form explainers — the kind of thing a site keeps under /learn/: how to read a prediction-market price as a signal, Kelly sizing for binary markets, macro-indicator glossaries. Boring, stable, cheap to produce. That matters later.

A fifth of the panel could never be measured

102 of 524 inspections came back unusable. Not "no data" — an actual error, API_ERROR_403, with the message every Search Console user eventually meets:

"You do not own this site, or the inspected URL is not part of this property." 

The URL is fine. The property is wrong. Twenty-eight of the 119 sites in this fleet are multi-root: one apex plus up to 280 subdomains (aave.coinatlaslab.com, apenft.altcoinfundingdesk.com, and 5,700 more across the network). The Search Console property is registered on the apex. Pages served from aave.coinatlaslab.com are therefore outside every property I own, which is technically correct and operationally useless: the sitemap lists them, Google may well index them, and no coverage report will ever show either fact.

Of the 112 subdomain URLs in the panel, 101 returned 403 — 90%. They span 16 domains. The one non-403 error was a 500 on a retry-after-three-attempts host.

The obvious fix is a property per subdomain, and it does not scale: 5,739 roots would need 5,739 properties. The cheaper fix is to stop putting subdomain URLs in an apex sitemap you cannot report on, or to front them with a canonical to an apex URL you can. Either way, the instrumentation has to check host ownership before it trusts a coverage state:

from urllib.parse import urlparse def owner(url, prop): """apex | sub — sub means the property cannot inspect this URL at all.""" return "apex" if urlparse(url).netloc == urlparse(prop).netloc else "sub" def inspect(url, prop, token): body = json.dumps({"inspectionUrl": url, "siteUrl": prop, "languageCode": "en-US"}).encode() req = urllib.request.Request( "https://searchconsole.googleapis.com/v1/urlInspection/index:inspect", data=body, method="POST", headers={"Authorization": "Bearer " + token, "Content-Type": "application/json"}) try: with urllib.request.urlopen(req, timeout=45) as r: res = json.load(r)["inspectionResult"]["indexStatusResult"] return res.get("coverageState", "UNKNOWN") except urllib.error.HTTPError as e: # 403 = not my property. Do not fold this into "not indexed".  return "API_ERROR_%d" % e.code 

The important line is the comment. If you count a 403 as a coverage state, your index rate silently improves whenever you sample more subdomains, because the failures vanish from the denominator. Mine did exactly that for three weeks before I looked.

What actually moved

Denominator 423 → 422, so the delta is not an artifact of dropped rows:

coverageState 10-15 10-30 change
Submitted and indexed 164 (38.8%) 247 (58.5%) +19.8 pt
Discovered – not indexed 160 (37.8%) 82 (19.4%) −18.4 pt
URL unknown to Google 94 (22.2%) 84 (19.9%) −2.3 pt
Crawled – not indexed 1 0 −0.2 pt
Soft 404 0 2 +0.5 pt
Page with redirect 0 2 +0.5 pt
Duplicate, no user canonical 0 3 +0.7 pt

150 of 524 URLs changed state. The forward bucket is the one you want: 67 went Discovered → Indexed, 17 went Unknown → Indexed, 1 came back from Crawled. The backward bucket is the one the dashboard hides: 33 URLs went Discovered → Unknown, meaning Google dropped even the queued-crawl record it previously had. Add the new soft 404s, redirects and duplicates and 42 of the 150 transitions were regressions.

A network-level "+20 points" and "42 URLs quietly lost their place in the crawl queue" are the same dataset. Only one of them fits on a chart.

Five sites did half of it

from collections import Counter def per_site(rows, prop_key="site"): n, idx = Counter(), Counter() for r in rows: n[r[prop_key]] += 1 idx[r[prop_key]] += r["coverage"] == "Submitted and indexed" return {s: (idx[s], n[s]) for s in n} base, cur = per_site(rows_1015), per_site(rows_1030) delta = {s: cur[s][0] - base[s][0] for s in cur} gain = sum(d for d in delta.values() if d > 0) # 85 top5 = sum(sorted((d for d in delta.values() if d > 0), reverse=True)[:5]) print(top5 / gain) # 0.506 print(sum(1 for d in delta.values() if d == 0)) # 24 sites, unmoved 

Nineteen sites up, 24 flat, 2 down. Gross gain 85, gross loss 2, net 83. Half the gain sits in five domains: predictmarketcn.com 0→10, eventmarketcn.com 0→9, predictionlabcn.com 0→9, marketforecastcenter.com 1→9, marketpredictionnow.com 0→7.

Now the part I cannot explain well. Those five sites share a template, a deploy pipeline, and a sitemap generator with the eight sites that stayed at zero. markettrendlab.com publishes 1,111 sitemap URLs — a per-coin technical and fundamental analysis archive — and indexed 0 of 10 sampled pages in both rounds. coinatlaslab.com has 1,612 pages and 0 of 10. predictioncn.com, predictionwikicn.com, marketoraclecenter.com, eventmarkethub.com, coinwikipro.com: also 0 of 10.

Same code, same host, same day. If your model of indexing is "publish, ping sitemap, wait", this table is telling you the model is missing a variable. I have not isolated it yet, and I am not going to pretend a fifteen-day window with one measurement per URL did.

The hypothesis that failed

The fleet-wide crawl I ran the same morning gave me a clean suspect: 915 of 67,543 pages (1.35%) had zero internal inlinks. 109 of 119 sites had at least one orphan, 16 sites were 100% orphaned, and orphan pages are exactly the ones that sit in Discovered forever. It should have predicted the index rate.

It did not. On the 45 sites in the panel, the correlation between orphan ratio and indexed ratio is r = 0.034 — indistinguishable from noise.

site orphan ratio indexed
marketoracledaily.com 0.00% 1/10
coinatlaslab.com 0.00% 0/10
polymarkettutorials.com 4.84% 0/10
predictionwikicn.com 7.03% 0/10

The two worst orphan ratios in the fleet have the same index rate as two sites with none. A 1.35% orphan rate is simply too small to move a ten-URL sample: you would need the orphans to be sampled, and at ten per site they mostly are not. I fixed nothing here, and the honest conclusion is that the orphan census was measuring the wrong population for this question — it is a link-graph health metric, not an indexing predictor.

The second experiment, which is less flattering

On 2026-10-16 I shipped server-rendered static output for eight single-page-app sites, so that crawlers would get real HTML instead of an empty <div id="root">. Fourteen days later I sampled 15 routes per site — 120 URLs, deterministic seed, drawn from the manifest of routes that were actually static-rendered:

result count share
Submitted and indexed 36 30.0%
URL unknown to Google 47 39.2%
Discovered – not indexed 25 20.8%
Duplicate / canonical conflicts 10 8.3%
Other (redirect, alternate canonical) 2 1.7%

Per site: coinpriceoracle.com 13/15, coinforecastpro.com 8/15, then bullrunalpha.com, cryptobullforecast.com, nextaltcoinhub.com at 5/15, and three sites at 0/15.

Thirty percent after two weeks is not a failure — new routes need discovery time, and the pre-change baseline for these same routes was not measured, which is the mistake I would undo first. But 39% of freshly rendered URLs are still unknown to Google, not "discovered, waiting". Google has no record of them at all. That is a discovery problem, not a quality problem, and no amount of on-page work fixes it.

What I changed in the instrumentation

Four rules, all of which cost me a wrong report first:

  1. Report per-site distribution, never the fleet average. 19 up / 24 flat / 2 down next to +19.8 pt is the whole story; either one alone is misinformation.
  2. Separate transport and permission errors from coverage states. A 403 is missing instrumentation, not a bad page. Keep it out of the denominator and print it as its own line.
  3. Snapshot the same URL panel. 524 URLs, identical both runs, made a 150-row transition matrix possible. Random sampling each time would have produced a plausible number and no explanation.
  4. Measure the baseline before shipping the fix. I did this for the panel and did not do it for the static-rendering rollout, so the most interesting change in the quarter has no before-row.

The fleet now sits at 58.5% indexed on the panel, with a fifth of its URLs structurally unmeasurable and a discovery gap on the newest pages that no dashboard will show me. Next window I am going after the 33 URLs that fell out of the crawl queue — those are the ones with an answer in them.