Every agency and in-house marketing team already keeps a spreadsheet of URLs — client sites, landing pages, competitors, a content backlog. This post turns that spreadsheet into a live SEO audit tool. You paste URLs into column A, click a custom menu item, and columns B through E fill in with the score, grade, and top fixes — pulled straight from the SEO Score API.

No server, no SDK install, no local environment. It runs entirely inside Google Sheets via Apps Script, which means anyone on the team — including the non-developers — can use it. That accessibility is the whole point.

What you'll build

A sheet that looks like this after one click:

URL Score Grade Top Issue Audited
https://example.com 87 B Meta description too short 2026-07-21
https://competitor.com 64 D Missing canonical tag 2026-07-21

A custom SEO Audit menu appears in the toolbar with a "Run audits" command. It walks every URL in column A, calls the API, and writes the results back. Re-runnable any time you add rows.

The script

In your sheet, open Extensions → Apps Script, delete the placeholder, and paste this:

const API_KEY = "your_seoscoreapi_key"; // from seoscoreapi.com/#signup

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu("SEO Audit")
    .addItem("Run audits", "runAudits")
    .addToUi();
}

function runAudits() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const rows = sheet.getDataRange().getValues();

  // Header row
  sheet.getRange(1, 1, 1, 5)
    .setValues([["URL", "Score", "Grade", "Top Issue", "Audited"]]);

  for (let i = 1; i < rows.length; i++) {
    const url = rows[i][0];
    if (!url) continue;

    try {
      const resp = UrlFetchApp.fetch(
        "https://seoscoreapi.com/audit?url=" + encodeURIComponent(url),
        {
          headers: { "X-API-Key": API_KEY },
          muteHttpExceptions: true,
        }
      );
      const data = JSON.parse(resp.getContentText());

      if (data.score === undefined) {
        sheet.getRange(i + 1, 2).setValue("error: " + (data.detail || data.error || "unknown"));
        continue;
      }

      const topIssue = (data.priorities && data.priorities[0])
        ? data.priorities[0].issue
        : "Clean — no priority issues";

      sheet.getRange(i + 1, 2, 1, 4).setValues([[
        data.score,
        data.grade,
        topIssue,
        new Date().toISOString().slice(0, 10),
      ]]);

      // Be polite to the rate limit — see note below
      Utilities.sleep(1500);
    } catch (e) {
      sheet.getRange(i + 1, 2).setValue("error: " + e.message);
    }
  }
}

Reload the sheet, and the SEO Audit menu appears. Put URLs in column A starting at row 2, click Run audits, and watch the columns fill.

The rate-limit gotcha that will bite you

This is the part everyone gets wrong on the first run. Apps Script executes the loop as fast as it can, and every plan has a requests-per-minute (RPM) limit: 2/min on Free, 10/min on Starter, 30/min on Basic. Without throttling, a 50-row sheet fires 50 requests in a couple of seconds and you'll get back a wall of 429 Too Many Requests errors.

That's what the Utilities.sleep(1500) line is for — it paces the loop to ~40 requests/minute, which is safe on Basic and above. Match the sleep to your plan: Free needs sleep(30000) (and realistically can't do bulk at all), Starter needs sleep(6000), Basic is fine at sleep(1500). Get this wrong and the sheet fills with errors; get it right and it just works.

Why won't my audits finish on a big list?

Apps Script caps a single execution at 6 minutes. With a 1.5-second pace, that's roughly 200 URLs per run before Google kills the script. For lists bigger than that, audit in batches: add a column that marks rows as done, and have runAudits skip already-audited rows so each click picks up where the last one left off. For genuinely large catalogs — thousands of pages — a spreadsheet is the wrong tool; use the batch audit endpoint from a real script instead.

How much does a Sheets workflow cost to run?

It depends entirely on how often you re-audit. A 100-URL competitor sheet audited weekly is 400 audits/month — that's the Basic plan ($15/mo). A small client roster audited monthly fits in Starter ($5/mo). The spreadsheet doesn't change the economics; the audit volume does. The honest tier math is in SEO Audit ROI: When Does Each Plan Pay for Itself?.

Adding conditional formatting that earns its keep

The numbers are more useful when the sheet colors them. Select the Score column, then Format → Conditional formatting, and add a color scale: red below 70, yellow 70–85, green above 85. Now a 100-row audit is scannable at a glance — the red rows are your work queue. This single step is what turns the sheet from a data dump into something a team actually acts on.

Can non-technical teammates use this?

That's the entire reason to build it in Sheets rather than as a script. Once you've pasted the Apps Script and set the key, the workflow is "paste URLs, click a menu, read the colors." No terminal, no Python, no API knowledge. An account manager can audit a client's whole site before a check-in call without involving a developer. For agencies, that's the difference between SEO auditing being a bottleneck and being self-serve — the same democratization argument behind white-label reports.

Where to take it next

Two natural extensions. Scheduled audits: Apps Script has time-based triggers, so you can run runAudits every Monday at 6am and have fresh scores waiting when the team logs in — though note this is automation, so keep an eye on the quota math. A second sheet for history: append each run's scores to a log tab instead of overwriting, and you've got a trend line per URL with zero extra infrastructure. That's a poor-but-functional version of the monitoring dashboard for teams that live in spreadsheets.

A spreadsheet everyone already knows how to use, plus one menu command, is often a better SEO tool than a dashboard nobody opens.