# SEO Audit Tool: Bulk Website Checker for CSV, Sheet or Dataset (`nerolabs/bulk-seo-audit`) Actor

Returns a 0-100 SEO score, grade and issues to fix (title, meta description, H1, noindex, HTTPS, mobile, schema, sitemap, broken links) for every website in an Apify dataset, CSV or Google Sheet, keeping your columns. Inputs: datasetId, fileUrl or urls. $0.005 per page. Agent-ready: x402, MCP.

- **URL**: https://apify.com/nerolabs/bulk-seo-audit.md
- **Developed by:** [Adam Pearce](https://apify.com/nerolabs) (community)
- **Categories:** SEO tools, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 page auditeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## SEO Audit Tool: Bulk Website Checker for CSV, Sheet or Dataset

Give it a list of websites, from a lead list, a Google Sheet, a CSV or another Actor's dataset, and get back a **0-100 SEO score, a grade and a plain-English list of what to fix** for every one, written onto your own rows. One run audits ten sites or fifty thousand.

Most SEO audit tools crawl one website at a time. This one is built for **lists**: agencies checking prospects, marketers checking a portfolio of client sites, and pipelines that run straight after a Google Maps or directory scrape.

### What you get for each website

| Column | What it tells you |
|---|---|
| `seoScore`, `grade` | 0-100 score and A to F grade. Starts at 100 and loses points per issue (weights below). |
| `issues`, `issueSummary` | Issue codes, plus a sentence for each one, worst first: "No meta description: Google will pick a snippet itself (-10)". |
| `pageTitle`, `titleLength` | The page title and its length (Google cuts titles after about 60 characters). |
| `metaDescription`, `metaDescriptionLength` | The meta description and its length. |
| `h1`, `h1Count`, `h2Count` | Main heading and heading counts. |
| `isHttps`, `hasViewport`, `noindex`, `robotsMeta`, `canonicalUrl` | The checks that decide whether Google can index and trust the page, and whether it works on phones. |
| `wordCount`, `imageCount`, `imagesMissingAlt` | Thin content and image alt text. |
| `hasOpenGraph`, `ogImage`, `hasTwitterCard` | Whether links shared on Facebook, LinkedIn and WhatsApp show a proper preview. |
| `structuredDataTypes` | Schema.org types found (LocalBusiness, Organization, Product...). |
| `robotsTxtFound`, `sitemapUrl` | Site-level checks, once per website. |
| `brokenLinkCount`, `brokenLinks` | Broken links on the page, when you switch the link check on. |
| `auditedUrl`, `finalUrl`, `responseTimeMs`, `pageSizeKb`, `httpStatus` | The address audited, where it redirected to, download time and size. |

**Every original column is kept**, so the company name, phone and address from your lead list sit next to the score. The audit's own columns have distinct names (`pageTitle`, `auditedUrl`), so a Google Maps `title` or `url` is never overwritten.

### Typical uses

- **Find prospects with weak websites**: run it on a Google Maps lead list and keep only scores below 70 (`keep: below_score`). Those are the businesses that need your SEO or web design service, each with a ready-made list of problems to open the conversation with.
- **Monthly client reporting**: audit every client site on a schedule, append to a named dataset and watch the scores over time.
- **Agent and automation step**: an AI agent or an n8n, Make or Zapier flow sends a list of URLs and gets structured scores back.

### Input

Use **one** of these:

- **Dataset**: pick any Apify dataset (for example a Google Maps scraper run). The website column is found automatically (`website`, `domain`, `url`...).
- **File or Google Sheet URL**: CSV, TSV, Excel, JSON or JSON Lines, or a Google Sheet shared as "Anyone with the link can view".
- **Website URLs**: a plain list for a quick check. Bare domains like `example.com` are fine.

Options: check for broken links (off by default), check robots.txt and sitemap (on), keep all rows / only audited / only below a score / only problems, CSV or Excel export, a webhook when the run finishes, and an optional proxy.

### Pricing

Pay per event, no subscription:

| Event | Price |
|---|---|
| Page audited (fetched and checked) | $0.005 (Bronze $0.0045, Silver $0.004, Gold $0.0035) |
| Link checked (only when the broken-link check is on) | $0.0005 |
| Export file (CSV or Excel, only if you ask) | $0.01 |

**Never charged**: rows with no website, values that are not web addresses, sites that do not exist or do not answer, error pages (404, 500), sites that refuse automated access, pages robots.txt asks us not to read, and repeats of a page already in the run.

Worked examples: 1,000 leads is **$5.00**. The same 1,000 with up to 50 links checked on each is about $30. A 20-row test list is $0.10.

### How the score works

The score starts at 100 and loses: noindex 25, missing title 15, not HTTPS 10, missing meta description 10, broken links 2 each (up to 10), missing H1 8, no mobile viewport 8, slow download over 3 s 5, thin content under 250 words 5, images without alt text 1 each (up to 5), title too long or short 3, several H1s 3, no canonical 3, no Open Graph 3, no structured data 3, no sitemap 3, HTML over 3 MB 3, meta description too long or short 2, canonical pointing elsewhere 2, no lang attribute 2, no robots.txt 2, no favicon 1.

### Good to know

- The page is read the way a search engine first reads it: the HTML as served, without running JavaScript. Sites built entirely in JavaScript can show few words and no headings; that is also what many crawlers see.
- This is an on-page audit. For Core Web Vitals and Lighthouse performance scores, use our [Bulk PageSpeed & Lighthouse Checker](https://apify.com/nerolabs/bulk-pagespeed-checker).
- Some sites put every visitor through a bot check (Cloudflare's "Just a moment..." page); those rows come back as `blocked` and cost nothing. Sites that only ask us to slow down (HTTP 429, common on Wix) are retried patiently before giving up.
- At most two requests at a time go to any one website.

### For AI agents

Pay per event through Apify's x402 and MCP support. Send `urls` (or `datasetId` / `fileUrl`), read `seoScore`, `grade`, `issues` and `auditedUrl` from the dataset, and the run summary (`AUDIT_SUMMARY`) gives the average score, grade bands and the most common issues across the list.

# Actor input Schema

## `datasetId` (type: `string`):

An Apify dataset with one row per website or page, for example a Google Maps or lead scraper's output. Every original column is kept and the SEO score, issues and checks are added alongside. Use the picker rather than typing an ID.

## `fileUrl` (type: `string`):

A public link to a CSV, TSV, Excel, JSON or JSON Lines file with one row per website or page. A normal Google Sheets link works: share it as 'Anyone with the link can view'. Used when no dataset is given.

## `fileFormat` (type: `string`):

Leave on 'Detect automatically' unless the link has no file extension and the server reports the wrong content type.

## `sheetName` (type: `string`):

Which sheet to read from an Excel workbook. Defaults to the first sheet.

## `urls` (type: `array`):

A plain list of websites or pages for a quick run with no spreadsheet (bare domains like example.com are fine). Use the dataset, file or Google Sheet inputs above to keep your own columns next to the results.

## `data` (type: `array`):

Rows as inline JSON, an alternative to a dataset or file. Each object needs a field holding the website or page.

## `urlField` (type: `string`):

The column holding the website or page to audit. Left empty it is detected automatically, preferring columns named website, domain, homepage or url.

## `checkSite` (type: `boolean`):

Also check each site once for a robots.txt file and an XML sitemap (from robots.txt, /sitemap.xml or /sitemap_index.xml). Free, a couple of extra requests per site.

## `checkLinks` (type: `boolean`):

Also test the links on each audited page and list the broken ones (404, 410, 5xx or no answer). Off by default because it takes longer; charged per link checked.

## `maxLinksPerPage` (type: `integer`):

The first this-many links on each page are tested when 'Check for broken links' is on.

## `keep` (type: `string`):

Filtering happens after each page is audited. 'Only scores below the threshold' is the quick way to pull prospects with weak SEO out of a lead list.

## `scoreThreshold` (type: `integer`):

Used by 'Only scores below the threshold'. Rows scoring below this are kept.

## `keepOriginalFields` (type: `boolean`):

Keep every column from the input row next to the extracted fields, so results line up with your own data. Turn off for article fields only.

## `respectRobotsTxt` (type: `boolean`):

Skip pages a site's robots.txt asks automated tools not to read (marked 'robots_disallowed', not charged). Turn off for your own or your clients' sites.

## `proxyConfiguration` (type: `object`):

Off by default and usually not needed. Some sites block by address or by country, where an Apify proxy can help. It does not get past blocks that check the browser itself.

## `concurrency` (type: `integer`):

How many pages to audit in parallel. Never more than two at a time go to any one site.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for one page before giving up. A timed-out page is never charged.

## `maxPageMb` (type: `integer`):

Pages bigger than this are skipped and not charged.

## `maxItems` (type: `integer`):

A hard ceiling on how many rows are read from the input, as a safety net on a large dataset.

## `exportFormats` (type: `array`):

Also write the results as a downloadable CSV or Excel file, linked from the run's output. $0.01 per file, charged only when the run audited at least one page.

## `outputDatasetName` (type: `string`):

Also append every kept row to a named dataset that persists across runs, for example a monthly SEO history. Not charged again.

## `webhookUrl` (type: `string`):

POST the run summary to this URL when the run finishes, for Slack, Zapier, Make, n8n or your own API. Charged only on a confirmed 2xx response.

## Actor input object example

```json
{
  "fileFormat": "auto",
  "urls": [
    "https://www.apify.com",
    "https://www.bbc.co.uk",
    "https://example.com"
  ],
  "checkSite": true,
  "checkLinks": false,
  "maxLinksPerPage": 50,
  "keep": "all",
  "scoreThreshold": 70,
  "keepOriginalFields": true,
  "respectRobotsTxt": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "concurrency": 5,
  "requestTimeoutSecs": 30,
  "maxPageMb": 10,
  "exportFormats": []
}
```

# Actor output Schema

## `results` (type: `string`):

Every original row with the 0-100 SEO score, grade, issues to fix and every on-page check added.

## `auditSummary` (type: `string`):

Pages audited, average score, grade bands, most common issues, export links and warnings.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://www.apify.com",
        "https://www.bbc.co.uk",
        "https://example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("nerolabs/bulk-seo-audit").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "urls": [
        "https://www.apify.com",
        "https://www.bbc.co.uk",
        "https://example.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("nerolabs/bulk-seo-audit").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://www.apify.com",
    "https://www.bbc.co.uk",
    "https://example.com"
  ]
}' |
apify call nerolabs/bulk-seo-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nerolabs/bulk-seo-audit"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/w5GsFHbebsySqiW6u/builds/banum5llmBl9YGq2q/openapi.json
