<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: SEO by Subham</title>
    <description>The latest articles on DEV Community by SEO by Subham (@seobysubham).</description>
    <link>https://dev.to/seobysubham</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4090679%2F03f6e2a1-77cd-4b12-ba57-7e2bc24c2ac0.png</url>
      <title>DEV Community: SEO by Subham</title>
      <link>https://dev.to/seobysubham</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/seobysubham"/>
    <language>en</language>
    <item>
      <title>Your Backlinks Are Indexed But Invisible to AI Search — I Built a Free Checker to Prove It</title>
      <dc:creator>SEO by Subham</dc:creator>
      <pubDate>Fri, 28 Aug 2026 00:31:23 +0000</pubDate>
      <link>https://dev.to/seobysubham/your-backlinks-are-indexed-but-invisible-to-ai-search-i-built-a-free-checker-to-prove-it-43p0</link>
      <guid>https://dev.to/seobysubham/your-backlinks-are-indexed-but-invisible-to-ai-search-i-built-a-free-checker-to-prove-it-43p0</guid>
      <description>&lt;h2&gt;
  
  
  The Backlink That Google Loved and ChatGPT Never Heard Of
&lt;/h2&gt;

&lt;p&gt;A few weeks back I published &lt;a href="https://dev.to/seobysubham/i-built-a-google-sheets-tool-to-check-backlink-indexation-with-serpapi-and-why-i-made-it-1h0k"&gt;a Google Sheets tool to check backlink indexation with SerpApi&lt;/a&gt; — a simple, manual-only script that tells you whether a backlink has actually been picked up by Google, instead of guessing.&lt;/p&gt;

&lt;p&gt;It worked well. One backlink I was tracking got indexed by Google in under 72 hours. Good news, right?&lt;/p&gt;

&lt;p&gt;Then, out of curiosity, I opened ChatGPT and asked it about the exact page that backlink pointed to.&lt;/p&gt;

&lt;p&gt;It had no idea the page existed.&lt;/p&gt;

&lt;p&gt;Not "I don't have real-time data." Not a vague guess. It simply didn't know the content was there — despite Google indexing it days earlier.&lt;/p&gt;

&lt;p&gt;That gap is the entire premise of this post: &lt;strong&gt;being indexed by Google and being visible to AI search are two completely different things&lt;/strong&gt;, and almost nobody is measuring the second one.&lt;/p&gt;

&lt;p&gt;So I extended my original checker to test for both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "Indexed" Doesn't Mean "Visible" Anymore
&lt;/h2&gt;

&lt;p&gt;For over a decade, indexation was the finish line. If Google crawled your page and added it to the index, you were in the game — rankings were just a matter of time and optimization from there.&lt;/p&gt;

&lt;p&gt;That assumption is breaking down.&lt;/p&gt;

&lt;p&gt;A growing share of search now happens inside AI systems — ChatGPT, Perplexity, and Google's own AI Overviews — that don't work like the traditional index. They don't list ten blue links; they synthesize an answer from a handful of sources they trust enough to cite. A page can be perfectly indexed and still never make it into that shortlist.&lt;/p&gt;

&lt;p&gt;For anyone doing SEO or link building, that means a new failure mode nobody's watching for: a backlink that "worked" by every traditional metric, but contributes zero visibility where a growing number of searches actually happen.&lt;/p&gt;

&lt;p&gt;I wanted a way to see that gap directly, on my own sites, without relying on vibes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built: An Indexation + AI Visibility Checker
&lt;/h2&gt;

&lt;p&gt;The original tool answered one question per backlink: &lt;em&gt;is this indexed?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The updated version answers three:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Is the backlink indexed by Google?&lt;/strong&gt; (unchanged from the original tool — SerpApi &lt;code&gt;site:&lt;/code&gt; query)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does the target page appear in Google's AI Overview&lt;/strong&gt; for the keywords it's meant to rank for?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does a general-purpose LLM (ChatGPT/Perplexity via API) recognize the page or domain at all&lt;/strong&gt; when asked directly?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each backlink row in the Google Sheet ends up with three simple signals instead of one, plus a combined score so I can sort and prioritize at a glance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Walkthrough
&lt;/h2&gt;

&lt;p&gt;Same stack as before: Python, the SerpApi library, and Google Sheets as the interface — no dashboard, no database, nothing to maintain.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Indexation check (recap)
&lt;/h3&gt;

&lt;p&gt;This part is unchanged from the original tool — a straightforward &lt;code&gt;site:&lt;/code&gt; search through SerpApi to confirm the URL is indexed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_indexation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serpapi_key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;engine&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;site:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;serpapi_key&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://serpapi.com/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;organic_results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Google AI Overview check
&lt;/h3&gt;

&lt;p&gt;SerpApi's Google engine returns an &lt;code&gt;ai_overview&lt;/code&gt; block when one is present for a query. I check whether my domain shows up anywhere inside its sources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_ai_overview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serpapi_key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;engine&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;serpapi_key&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://serpapi.com/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;ai_overview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai_overview&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="n"&gt;sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ai_overview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;references&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

    &lt;span class="n"&gt;cited&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_domain&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;has_ai_overview&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ai_overview&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domain_cited&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cited&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there's no AI Overview at all for that keyword yet, I record that too — it's a useful signal on its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Direct LLM recognition check
&lt;/h3&gt;

&lt;p&gt;This one is more experimental, and I want to be upfront about that. I send a small, consistent prompt to the OpenAI API asking whether the model has knowledge of the page or domain, and log the raw response for manual review rather than trying to auto-parse a yes/no:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_OPENAI_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_llm_recognition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Are you aware of the website or page at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(domain: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)? If so, briefly describe what it&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s about. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;If you have no knowledge of it, say so directly.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I run the same prompt structure against Perplexity's API too, since its answers are grounded in live search rather than training data alone — it tells me something different: not "does the model remember this," but "would this page surface in a real-time AI search right now."&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Combining it into one sheet
&lt;/h3&gt;

&lt;p&gt;Each backlink gets logged as a row with:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Backlink URL&lt;/th&gt;
&lt;th&gt;Indexed?&lt;/th&gt;
&lt;th&gt;AI Overview exists?&lt;/th&gt;
&lt;th&gt;Domain cited in Overview?&lt;/th&gt;
&lt;th&gt;LLM recognizes page?&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I don't collapse this into a single fake "score out of 100" — SEO tools do that too often and it hides more than it reveals. Instead I flag any row where &lt;strong&gt;Indexed = Yes&lt;/strong&gt; but both AI columns are &lt;strong&gt;No&lt;/strong&gt;. Those are the interesting rows. That's the actual gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Found Running It
&lt;/h2&gt;

&lt;p&gt;Out of the backlinks I re-checked from my last batch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every single one was indexed by Google, as expected.&lt;/li&gt;
&lt;li&gt;Roughly a third appeared in an AI Overview for their target keyword.&lt;/li&gt;
&lt;li&gt;Almost none were recognized directly by the LLM when asked cold — which makes sense, since a backlink built last month isn't in any model's training data yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI Overview number was the useful one. It's the closest thing to a real-time "does AI search actually see this" signal available right now, and it's a much smaller percentage than the indexation rate. That gap is exactly what I was trying to surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is Still Manual-Only
&lt;/h2&gt;

&lt;p&gt;Same reasoning as the original tool, and it applies even more here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API costs add up fast.&lt;/strong&gt; LLM calls aren't free, and neither are SerpApi credits. Automating this into a daily cron job for hundreds of URLs would get expensive quickly for very little marginal insight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI answers aren't stable.&lt;/strong&gt; Ask the same question twice and you can get different phrasing, or a different verdict entirely. Automating on top of a non-deterministic signal just produces noisy automation, not a reliable pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This is a diagnostic, not a dashboard.&lt;/strong&gt; I run it in batches when I want a checkpoint, review the raw answers myself, and move on. That's a better use of the signal than pretending it's precise enough to monitor continuously.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Manual-only isn't a limitation here — it's the correct way to use a signal that's still this noisy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations, Honestly
&lt;/h2&gt;

&lt;p&gt;A few things worth being upfront about, since overselling this would defeat the point of writing it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLM "recognition" is not a real index. There's no API that tells you definitively what a model does or doesn't know — asking it directly is a proxy, not ground truth.&lt;/li&gt;
&lt;li&gt;AI Overviews don't appear for every query, and their presence/absence can shift between runs.&lt;/li&gt;
&lt;li&gt;None of this replaces actual traffic and conversion data. It's a leading indicator, not proof of impact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building something similar, treat every AI-visibility signal as directional, not authoritative.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The full script (indexation + AI Overview + LLM recognition check) is a single Python file plus a Google Sheet template — no server, no signup, nothing to host. If you want to run it on your own backlinks, I'm happy to share the setup in the comments or a follow-up post.&lt;/p&gt;

&lt;p&gt;If you've noticed the same indexed-but-invisible gap on your own sites, I'd genuinely like to hear what you found — drop it below.&lt;/p&gt;

&lt;p&gt;I write and build tools like this as part of my link-building work at &lt;a href="https://seobysubham.com" rel="noopener noreferrer"&gt;SEO by Subham&lt;/a&gt; — if you're curious about the manual, white-hat approach behind the original indexation checker too, it's all there.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>python</category>
      <category>googlesheet</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I built a Google Sheets tool to check backlink indexation with SerpApi, and why I made it manual-only</title>
      <dc:creator>SEO by Subham</dc:creator>
      <pubDate>Sun, 23 Aug 2026 10:40:55 +0000</pubDate>
      <link>https://dev.to/seobysubham/i-built-a-google-sheets-tool-to-check-backlink-indexation-with-serpapi-and-why-i-made-it-1h0k</link>
      <guid>https://dev.to/seobysubham/i-built-a-google-sheets-tool-to-check-backlink-indexation-with-serpapi-and-why-i-made-it-1h0k</guid>
      <description>&lt;p&gt;I do SEO and link-building work at &lt;a href="https://seobysubham.com/" rel="noopener noreferrer"&gt;seobysubham.com&lt;/a&gt;, and one task kept feeling unnecessarily repetitive: checking whether the pages containing backlinks were actually indexed by Google.&lt;/p&gt;

&lt;p&gt;The process is simple in theory. Take a list of backlink URLs, search for each one, and determine whether Google has the page in its index. Doing that manually for a large list is slow, especially when the URLs are already sitting in a Google Sheet. That was the main reason I decided to build the checker directly inside Sheets rather than as a separate app.&lt;/p&gt;

&lt;p&gt;I didn't want another small web application with its own login, database, hosting, and UI. The people using the data already had a spreadsheet. Putting the results there meant there was no extra interface to learn, no separate auth flow, and the rest of the team could see results in the same document they already worked from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Google Apps Script?
&lt;/h2&gt;

&lt;p&gt;Apps Script was a natural fit because it sits directly inside Google Workspace. A spreadsheet can have an Apps Script project attached to it, and that script can read and write cells using the Sheets API available through the Apps Script runtime. It also lets me add a custom menu to the spreadsheet, so the checker starts from the UI instead of running as a completely separate application.&lt;/p&gt;

&lt;p&gt;The architecture is deliberately small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Google Sheet
    |
    | Backlink URLs
    v
Apps Script
    |
    | HTTP request
    v
SerpApi
    |
    | Search response
    v
Apps Script
    |
    | Parse result
    v
Google Sheet
    |
    +-- Indexed
    +-- Not Indexed
    +-- Error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No dedicated server involved. The spreadsheet is effectively the interface, and Apps Script handles the application logic. Backlink URLs live in one column, indexation results get written into another.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core logic
&lt;/h2&gt;

&lt;p&gt;The production version has more validation and error handling, but the basic implementation is fairly straightforward. For every backlink URL, the script sends a request to SerpApi. The response is JSON, so the script can inspect the returned search results and decide whether the target URL appears in Google's results.&lt;/p&gt;

&lt;p&gt;A simplified version looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkBacklinkIndexation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getActiveSpreadsheet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSheetByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Backlinks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PropertiesService&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getScriptProperties&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SERPAPI_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDataRange&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getValues&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Assume column A contains backlink URLs&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;backlinkUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;backlinkUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`"&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;backlinkUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://serpapi.com/search.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;?engine=google&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;q=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;api_key=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UrlFetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;muteHttpExceptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContentText&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;indexed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;organic_results&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;backlinkUrl&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;indexed&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Indexed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not Indexed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is intentionally simplified. In a real implementation, I wouldn't rely on an exact string comparison alone, since URLs can differ by trailing slashes, query parameters, redirects, encoding, or canonical versions. The important part is the data flow rather than this exact comparison: read the rows, skip empty URLs, build a search request, send it to SerpApi, parse the JSON response, determine whether the target appears in the returned results, and write the status back into the spreadsheet.&lt;/p&gt;

&lt;p&gt;I also keep the API key in Apps Script's script properties rather than hardcoding it into the source. That makes configuration easier to manage and avoids accidentally leaving the key inside a function that might get copied around.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I intentionally made it manual-only
&lt;/h2&gt;

&lt;p&gt;This was probably the most deliberate engineering decision in the project.&lt;/p&gt;

&lt;p&gt;Apps Script supports time-driven triggers, so technically I could make the checker run automatically every hour, every day, or on some other interval. I decided not to. The reason is API cost.&lt;/p&gt;

&lt;p&gt;Every backlink check results in an external API query, and SerpApi charges per search. An automatic trigger can quietly consume the available quota. Imagine a spreadsheet with 500 URLs — if an automated process checks all of them daily, that's potentially 15,000 searches a month, and the user might not notice until the quota is already gone.&lt;/p&gt;

&lt;p&gt;For this particular tool, automation isn't necessarily an improvement. So instead, I added a custom menu and the user explicitly starts the process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open spreadsheet
       |
       v
Review backlink list
       |
       v
Run "Check Indexation"
       |
       v
API requests are made
       |
       v
Results appear in the sheet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes API usage visible, and it gives me better control when testing. I can add a few URLs, run the checker, inspect the output, and stop there. The trade-off is obvious: manual execution is less convenient than a scheduled job. But here I preferred predictable API usage over invisible automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technical problem I ran into
&lt;/h2&gt;

&lt;p&gt;One issue appeared once I started testing with larger backlink lists. The first implementation treated the spreadsheet almost like a normal JavaScript array and made an individual request for every row, followed immediately by an individual spreadsheet write. That works fine for a small test. It becomes inefficient with hundreds of URLs, because there are two different external operations happening repeatedly — a sheet read, an API request, a sheet write, over and over.&lt;/p&gt;

&lt;p&gt;Apps Script has execution time limits, and every network request adds latency. Writing to Sheets cell-by-cell also creates unnecessary overhead.&lt;/p&gt;

&lt;p&gt;The fix was to read the spreadsheet data once and collect results in memory instead of writing on every iteration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;checkUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then write the complete result range in a single operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;sheet&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Small change, but it matters once the spreadsheet gets larger. I also learned that API response handling needs to be defensive — a successful HTTP request doesn't automatically mean the expected JSON structure exists. An API can return an error, a quota message, or a response missing the expected fields. So the production version checks the HTTP response and expected fields before deciding a URL is "Not Indexed." An API failure and a confirmed non-indexed URL are two completely different states, and conflating them silently would've made the data untrustworthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd improve next
&lt;/h2&gt;

&lt;p&gt;A few things I'd change if I keep developing this.&lt;/p&gt;

&lt;p&gt;Batching. Large lists should be processed in controlled chunks rather than treating the entire spreadsheet as one job.&lt;/p&gt;

&lt;p&gt;Caching. If I checked a URL yesterday and it hasn't changed, there's little reason to spend another API query re-checking it. Storing the last-checked date and result would let me re-check only URLs that actually need another lookup.&lt;/p&gt;

&lt;p&gt;Better error reporting. Instead of just writing "Error," the sheet could store the actual HTTP status, a quota message, or a note on the malformed response — useful context instead of a dead end.&lt;/p&gt;

&lt;p&gt;A progress indicator. Since API requests take time, something like "42 / 200 checked" would make a manual run easier to follow.&lt;/p&gt;

&lt;p&gt;The basic version is already useful because it removes a repetitive lookup process from the browser and puts the result directly next to the backlink data it's describing. The bigger lesson for me was that a small internal tool doesn't always need a complicated architecture — Google Sheets, Apps Script, and one external API were enough. The more interesting engineering decisions were around API cost, execution limits, error states, and knowing when automation actually helps versus when it just hides a cost you'll notice later.&lt;/p&gt;

&lt;p&gt;Making the tool manual-only wasn't a limitation I forgot to solve. It was the feature that kept the system predictable.&lt;/p&gt;

</description>
      <category>googleappsscript</category>
      <category>javascript</category>
      <category>automation</category>
      <category>seo</category>
    </item>
  </channel>
</rss>
