# SEO Site Audit: Broken Links, Meta Tags & Speed (`cliqtomedia/seo-site-audit`) Actor

Crawl a website and get a prioritized SEO audit: status codes, redirect chains, broken links, titles, meta descriptions, canonical, hreflang, headings, page size and optional PageSpeed scores.

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

## Pricing

from $2.80 / 1,000 audited pages

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 Site Audit crawls one website from a start URL and checks every page it finds: HTTP status, redirect chain, broken links, title, meta description, canonical, hreflang, headings, noindex and page size. You get one row per page with a list of issues sorted by priority, plus one readable report for the whole site. No login and no API key are needed; a 20-page audit of apify.com took about 10 seconds and costs about $0.08.

Use it before a release, after a migration or every week to catch broken links and meta tag problems. The smallest useful run is one website URL with `maxPages` 20.

![SEO Site Audit: your website URL goes in, every page gets a status code, score and a list of issues to fix, with broken links and optional PageSpeed scores](https://api.apify.com/v2/key-value-stores/af0IciZEmsXmZo6kl/records/README-HERO-20261017.png)

### Table of contents

- [What it does](#what-it-does)
- [Quick start](#quick-start)
- [Input](#input)
- [Output](#output)
- [Pricing](#pricing)
- [Use cases](#use-cases)
- [Scheduling and monitoring](#scheduling-and-monitoring)
- [API, export and integrations](#api-export-and-integrations)
- [Limits and partial results](#limits-and-partial-results)
- [Troubleshooting](#troubleshooting)
- [FAQ](#faq)
- [Related Actors](#related-actors)
- [Support](#support)

### What it does

The Actor works like a small search-engine crawler. It opens the start URL, follows links to other pages on the same site and checks each page. It does not run a browser: it reads the HTML that the server sends.

For every page it returns:

- **Status and redirects:** the HTTP status code, every redirect hop (`redirectChain`) and the final URL.
- **Links:** broken internal links (4xx, 5xx or no answer) and broken external links (4xx, 5xx or a domain that does not exist), with the page where each one was found.
- **Meta tags:** title and its length, meta description and its length, canonical, meta robots and `X-Robots-Tag`, noindex, nofollow, `lang`, hreflang.
- **Content:** the first H1, H1 and H2 counts, word count, images without an `alt` attribute.
- **Speed signals:** response time, time to first byte and HTML size. Optional Google PageSpeed (Lighthouse) score and Core Web Vitals with your own free Google API key.
- **Issues:** a list of found problems (`issues[]`) with a code, severity (`critical`, `high`, `medium`, `low`) and a short message, plus a page score from 0 to 100.

After the crawl, the Actor writes a site report: issues grouped by type and sorted by priority and number of pages, site-level issues (duplicate titles, missing sitemap, robots.txt problems), the worst pages and all broken links.

The crawler respects `robots.txt` ([RFC 9309](https://www.rfc-editor.org/rfc/rfc9309)) and `Crawl-delay`, sends at most 5 requests at the same time and uses an honest User-Agent: `seo-site-audit-apify-actor/0.1 (+https://apify.com/cliqtomedia/seo-site-audit)`.

#### Good fit

- Find broken links and the pages that contain them before users or search engines do.
- Check titles, meta descriptions, canonical tags and noindex on every page after a redesign or migration.
- Find redirect chains and internal links that point to redirects.
- Get a quick technical SEO health check of a client or competitor site.

#### Not a fit

- Sites that build their content with JavaScript only. The Actor reads the server HTML, so text and links added later by scripts are not seen.
- Pages behind a login, a paywall or bot protection that blocks normal crawlers.
- Keyword research, rankings, backlinks or traffic data. This Actor checks your pages, not search results.
- Many websites in one run. Use one run per website.

### Quick start

1. Open the **Input** tab.
2. Add your website to **Website URL** (`startUrl`), for example `https://example.com`.
3. Keep **Max pages** (`maxPages`) at the suggested 20 for a first test.
4. Click **Start**.
5. Open the **Output** tab. The **Pages by priority** view shows the pages with the lowest score first (rows are saved in that order). Open the **Audit report (HTML)** for a readable summary.

Expected result: one row for each page that was checked, each with a status code, score and issue list.

### Input

#### Input fields

| Field | Name in the form | Type | Required | Default | Allowed values | What it changes | Recommendation |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `startUrl` | Website URL | string | yes | — (form suggests `https://apify.com`) | public `http` or `https` URL, port 80 or 443, no login data | Where the crawl starts. Only pages on the same host are crawled; `www` and non-`www` count as the same host. Without `https://` the Actor adds it. | Use the homepage. |
| `maxPages` | Max pages | integer | no | 100 (form suggests 20) | 1–2000 | Maximum number of URLs to check. This is also the maximum number of charged pages. | Start with 20, then use the real size of the site. |
| `maxDepth` | Max link depth | integer | no | empty = no limit | 0–50 | How many clicks away from the start URL to go. `0` checks only the start URL. | Leave empty for a full audit. |
| `checkExternalLinks` | Check external links | boolean | no | `true` | `true` / `false` | Checks links to other websites and reports broken ones. | Keep on; turn off for a faster run. |
| `maxExternalLinks` | Max external links to check | integer | no | 300 | 0–5000 | Unique external URLs to check in one run (at most 20 per checked page). External checks are free. | Raise it for large sites with many outbound links. |
| `includeSubdomains` | Include subdomains | boolean | no | `false` | `true` / `false` | Also crawls linked subdomains such as `blog.example.com`. | Turn on when the blog or shop lives on a subdomain. |
| `excludeUrlPatterns` | Exclude URLs | array of strings | no | empty | up to 50 strings, each up to 200 characters, `*` is a wildcard | Skips URLs that contain any of these strings. The start URL is always checked. | Use for `*/tag/*`, `?page=`, search pages and calendars. |
| `maxConcurrency` | Concurrent requests | integer | no | 5 | 1–5 | Requests to the site at the same time. When `robots.txt` has `Crawl-delay`, the Actor uses 1 and waits between pages (up to 10 s). | Lower it for small or slow servers. |
| `userAgent` | User-Agent | string | no | the Actor's own name (see above) | one line, up to 300 characters; names of search-engine bots are not allowed | The User-Agent header sent to the site. `robots.txt` rules for this name apply. | Keep the default unless your firewall needs a special name. |
| `pagespeedApiKey` | Google PageSpeed API key (optional) | string, secret | no | empty | Google API key (letters, digits, `-`, `_`) | Adds a Lighthouse performance score and Core Web Vitals for the first pages. The key is never logged or saved. | Get a free key in [Google Cloud](https://developers.google.com/speed/docs/insights/v5/get-started). |
| `pagespeedMaxPages` | PageSpeed pages | integer | no | 1 | 1–10 | How many checked pages get a PageSpeed test, in crawl order (start URL first). A test usually takes 10–30 s; a test that runs over 60 s is stopped. | 1–3 pages are enough for a template check. |
| `pagespeedStrategy` | PageSpeed device | string (enum) | no | `mobile` | `mobile`, `desktop` | Lighthouse device profile. | Google ranks with mobile; keep `mobile`. |

Unknown input fields are rejected with a clear message, so a typo never starts a paid crawl. Duplicate URLs, URLs with `#fragments` and links that end on an already checked page are checked once.

#### Settings that work together

| When | Field A | Field B | What happens |
| --- | --- | --- | --- |
| Both are set | `maxPages` | `maxDepth` | The crawl stops at whichever limit comes first. |
| No API key | `pagespeedApiKey` | `pagespeedMaxPages`, `pagespeedStrategy` | No PageSpeed tests and no PageSpeed charges; the other two fields are ignored. |
| External checks off | `checkExternalLinks` = `false` | `maxExternalLinks` | No external URLs are checked; `maxExternalLinks` is ignored. |
| `robots.txt` has `Crawl-delay` | `maxConcurrency` | — | The Actor uses 1 request at a time and waits between pages, so the run is slower. |
| A run charge limit is set | Max total charge (run option) | `maxPages` | The Actor checks only as many pages as the limit pays for and stops with `CHARGE_LIMIT`. |

#### Input examples

##### Quick check of a site

```json
{
  "startUrl": "https://apify.com",
  "maxPages": 20
}
```

The Actor checks 20 pages of apify.com and their external links. This is the form example; in our tests it finished in about 10 seconds and returned 20 rows.

##### Full audit with PageSpeed, without tag pages

```json
{
  "startUrl": "https://example.com",
  "maxPages": 500,
  "excludeUrlPatterns": ["*/tag/*", "?page="],
  "pagespeedApiKey": "YOUR_GOOGLE_API_KEY",
  "pagespeedMaxPages": 3
}
```

The Actor checks up to 500 pages, skips tag and pagination URLs and runs PageSpeed for the first 3 pages. In our test a 500-page audit took about 85 seconds without PageSpeed; your time depends on the site.

### Output

#### Where the results are

| What | Where | What it contains |
| --- | --- | --- |
| One row per page | Dataset, views **Pages by priority**, **Issues**, **Status, redirects & links** | All fields below. |
| Readable report | Key-value store, `REPORT_HTML` | Issues sorted by priority, worst pages, broken links, PageSpeed. |
| Report as JSON | Key-value store, `REPORT` | The same report for scripts and AI tools. |
| Run summary | Key-value store, `RUN_SUMMARY` | `status`, `stopReason`, `message`, page and link counters, `siteHealthScore`, `issuesBySeverity`, `pagesCharged`. |

#### Output example

One row from the apify.com example run (some fields left out):

```json
{
  "url": "https://apify.com/integrations",
  "finalUrl": "https://apify.com/integrations",
  "status": "ok",
  "statusCode": 200,
  "redirectChain": [],
  "depth": 1,
  "title": "Connect Apify with your apps · Apify",
  "titleLength": 36,
  "metaDescription": "Integrate the Apify platform and Actors with your favorite tools, APIs, and cloud services. Trigger workflows, send data in real time, and power your AI and apps with live web automation.",
  "metaDescriptionLength": 187,
  "h1Count": 1,
  "canonical": "https://apify.com/integrations",
  "isCanonicalSelf": true,
  "noindex": false,
  "hreflang": [],
  "wordCount": 1674,
  "responseTimeMs": 162,
  "pageSizeBytes": 949320,
  "brokenLinks": [
    { "url": "https://www.relay.app/apps/apify/integrations", "type": "external", "statusCode": 404, "error": null },
    { "url": "https://block.github.io/goose/docs/mcp/apify-mcp/", "type": "external", "statusCode": 404, "error": null }
  ],
  "brokenLinksCount": 2,
  "pagespeed": null,
  "issues": [
    { "code": "broken_external_links", "severity": "medium", "message": "2 broken external links" },
    { "code": "meta_description_too_long", "severity": "low", "message": "Meta description has 187 characters" },
    { "code": "restricted_external_links", "severity": "low", "message": "6 external links could not be verified (401/403/429, timeout or redirect loop)" }
  ],
  "issuesCount": 3,
  "topSeverity": "medium",
  "score": 88,
  "error": null,
  "fetchedAt": "2026-10-17T08:21:08.535Z"
}
```

`null` means "not available", not zero: for example, `canonical` is `null` when the page has no canonical tag, and content fields are `null` for non-HTML files and failed pages. `pagespeed` is `null` when no test was run for the page.

#### Output fields

| Field | Meaning | Type | Can be null? | Example |
| --- | --- | --- | --- | --- |
| `url` | Checked URL | string | no | `https://apify.com/store` |
| `finalUrl` | URL after all redirects | string | no | `https://apify.com/store` |
| `status` | `ok` = got an HTTP answer, `error` = no answer (DNS, timeout) or blocked | string | no | `ok` |
| `statusCode` | HTTP status of the final answer | integer | yes, when no answer | `200` |
| `redirectChain` | Every redirect hop: `url`, `statusCode` | array | no (empty when no redirect) | `[{"url":"http://…","statusCode":301}]` |
| `depth` | Clicks from the start URL | integer | no | `1` |
| `linkedFrom` | First page that links here | string | yes, for the start URL | `https://apify.com/` |
| `contentType`, `isHtml` | Response type | string, boolean | `contentType` yes | `text/html`, `true` |
| `responseTimeMs`, `ttfbMs` | Time to download the HTML / to the first byte | integer | yes | `162`, `12` |
| `pageSizeBytes` | HTML size (counted up to 5 MB) | integer | yes | `949320` |
| `title`, `titleLength` | Page title and its length in characters | string, integer | yes | `"Connect Apify with your apps · Apify"`, `36` |
| `metaDescription`, `metaDescriptionLength` | Meta description and its length | string, integer | yes | `"Connect Apify…"`, `187` |
| `h1`, `h1Count`, `h2Count` | First H1 text and heading counts | string, integer | yes | `"Connect Apify with your apps"`, `1`, `1` |
| `canonical`, `isCanonicalSelf` | Canonical URL and whether it points to the page itself | string, boolean | yes | `https://apify.com/integrations`, `true` |
| `noindex`, `nofollow` | From meta robots or `X-Robots-Tag` | boolean | yes | `false`, `false` |
| `robotsMeta`, `xRobotsTag` | Raw values | string | yes | `index,follow`, `null` |
| `lang` | `<html lang>` value | string | yes | `en` |
| `hreflang` | Alternate language links: `lang`, `url` | array | yes | `[]` |
| `wordCount` | Words of visible text in `<body>` | integer | yes | `1674` |
| `imagesMissingAlt` | `<img>` tags without an `alt` attribute (`alt=""` is fine for decorative images) | integer | yes | `0` |
| `internalLinksCount`, `externalLinksCount` | Links on this page | integer | yes | `45`, `103` |
| `inlinksCount` | Checked pages that link to this page | integer | no | `20` |
| `brokenLinks` | Broken links on this page: `url`, `type` (`internal`/`external`), `statusCode`, `error` | array | no (empty when none) | see example |
| `brokenLinksCount` | Number of broken links | integer | no | `2` |
| `pagespeed` | PageSpeed result: `performanceScore`, `largestContentfulPaintMs`, `cumulativeLayoutShift`, `totalBlockingTimeMs`, field data and more | object | yes | `null` |
| `issues` | Problems found: `code`, `severity`, `message`; most severe first | array | no | see example |
| `issuesCount`, `topSeverity` | Number of issues and the worst severity | integer, string | `topSeverity` yes | `3`, `medium` |
| `score` | Page score 0–100; each issue type lowers it (critical 40, high 20, medium 8, low 2) | integer | no | `88` |
| `error` | Why the page has no answer, for example `dns_failed`, `timeout`, `blocked_by_robots_txt` | string | yes | `null` |
| `fetchedAt` | When the page was checked (UTC) | string | no | `2026-10-17T08:21:08Z` |

#### Issue codes

Critical: `http_4xx`, `http_5xx`, `fetch_failed`, `redirect_loop`. High: `too_many_redirects`, `invalid_redirect`, `broken_internal_links`, `missing_title`, `canonical_to_broken`, `start_url_not_https`, `robots_txt_unavailable`. Medium: `redirect_chain`, `broken_external_links`, `missing_meta_description`, `missing_h1`, `multiple_titles`, `multiple_canonicals`, `canonical_invalid`, `noindex`, `hreflang_invalid`, `mixed_content`, `missing_viewport`, `slow_response`, `large_html`, `pagespeed_poor`, `duplicate_title`, `duplicate_meta_description`. Low: `restricted_external_links`, `redirected_internal_link`, `title_too_long` (over 60), `title_too_short` (under 30), `meta_description_too_long` (over 160), `meta_description_too_short` (under 70), `multiple_h1`, `missing_canonical`, `canonicalized`, `nofollow_meta`, `hreflang_missing_self`, `missing_lang`, `images_missing_alt`, `thin_content` (under 200 words), `missing_robots_txt`, `missing_sitemap`.

The HTML report explains each issue and how to fix it.

### Pricing

You pay per checked page, plus a small start fee. External link checks and robots.txt are free.

| Event | What it is | Free plan | Bronze | Silver | Gold |
| --- | --- | ---: | ---: | ---: | ---: |
| `apify-actor-start` | Once per run | $0.002 | $0.002 | $0.002 | $0.002 |
| `audited-page` | A page that returned an HTTP answer (any status code, including 404 and 301) | $0.004 | $0.0036 | $0.0032 | $0.0028 |
| `pagespeed-check` | A successful PageSpeed test (only with your API key) | $0.015 | $0.0135 | $0.012 | $0.0105 |

Pages that gave no answer (DNS error, timeout, connection error) and pages blocked by `robots.txt` are not charged. When the start URL answers 4xx or 5xx, that row is free and only the start fee is charged. Failed PageSpeed tests are not charged. Platform usage (compute, storage) is included in these prices.

#### Cost examples

| Scenario | Pages | Free plan | Bronze | Gold |
| --- | ---: | ---: | ---: | ---: |
| Quick check | 20 | $0.082 | $0.074 | $0.058 |
| Small site | 100 | $0.402 | $0.362 | $0.282 |
| Medium site | 500 | $2.002 | $1.802 | $1.402 |
| Large site | 2000 | $8.002 | $7.202 | $5.602 |
| Start URL not found | 0 | $0.002 | $0.002 | $0.002 |
| + PageSpeed for 3 pages | — | +$0.045 | +$0.0405 | +$0.0315 |

If you set **Max total charge** for a run, the Actor checks only as many pages as that amount pays for, stops with `CHARGE_LIMIT` and keeps all results.

### Use cases

| Job | Input | Key fields |
| --- | --- | --- |
| Fix broken links | `startUrl`, `checkExternalLinks` | `brokenLinks`, `brokenLinksCount`, `url` |
| Check meta tags after a redesign | `startUrl`, `maxPages` | `title`, `titleLength`, `metaDescription`, `canonical`, `noindex` |
| Clean up redirects after a migration | `startUrl` (old domain or new) | `redirectChain`, `finalUrl`, `statusCode` |
| Check hreflang on a multi-language site | `startUrl`, `includeSubdomains` | `hreflang`, `lang`, `issues` |
| Weekly site health check | `startUrl` on a schedule | `RUN_SUMMARY.siteHealthScore`, `issuesBySeverity` |
| Speed check of key templates | `pagespeedApiKey`, `pagespeedMaxPages` | `pagespeed.performanceScore`, `pagespeed.largestContentfulPaintMs` |

### Scheduling and monitoring

Create a task with your site, then add a schedule in Apify (for example every Monday). Each run is a fresh full audit. Compare `RUN_SUMMARY.siteHealthScore` and `issuesBySeverity` between runs, or connect a webhook that sends you the `REPORT_HTML` link when a run finishes. The Actor does not compare runs by itself.

### API, export and integrations

- **API:** start a run with `POST https://api.apify.com/v2/acts/cliqtomedia~seo-site-audit/runs` and the input JSON; read rows from the run's default dataset and the report from the key-value store keys `REPORT` and `RUN_SUMMARY`. See the [Apify API docs](https://docs.apify.com/api/v2).
- **Export:** download the dataset as JSON, CSV, Excel or HTML from the Output tab or the API.
- **No-code:** use the Apify integrations for Make, Zapier, n8n or Google Sheets to send rows with `brokenLinksCount > 0` to a sheet or a ticket.

#### Use with AI agents (MCP)

- MCP server with only this Actor: `https://mcp.apify.com?tools=cliqtomedia/seo-site-audit`
- Smallest useful input: `{"startUrl": "https://example.com", "maxPages": 20}`
- Key output: `url`, `statusCode`, `score`, `issues` (code, severity, message), `brokenLinks`. For a site overview read the `REPORT` record.
- In our tests a 20-page run took about 10 seconds and cost $0.058–$0.082, and a 500-page run took about 1.5 minutes. Times depend on the site. `maxPages` is at most 2000.

### Limits and partial results

- **HTML only, no JavaScript.** Titles, headings, links and text are read from the HTML the server sends. On sites that render content in the browser, H1, word count and links can be missing.
- **One site per run**, up to 2000 pages. Subdomains only with `includeSubdomains`.
- **robots.txt is always respected.** If `robots.txt` answers with a server error, the Actor checks only the start URL and reports `robots_txt_unavailable`.
- **Internal links** are checked when the linked page is crawled in the same run. Links to files (PDF, images and similar) are checked separately, up to 500 per run. Pages beyond `maxPages` are not checked.
- **External links:** up to `maxExternalLinks` and up to 20 per checked page. Answers 401, 403, 429 and 999, timeouts and loops are reported as `restricted_external_links` (could not verify), not as broken, because many sites block automated checks. Link checks stop after 10 timeouts in a row and take at most about 60 s plus 1 s per checked page; links left unchecked are counted in `RUN_SUMMARY.externalLinksSkipped`.
- **A redirect to another website** (for example to a login service) is a normal row with status 301 or 302 and is charged as one page.
- **HTML larger than 5 MB** is checked by its first 5 MB.
- **PageSpeed** depends on your Google key and quota. A failed test is saved as `pagespeed.status = "error"` with a reason such as `invalid_api_key` or `quota_exceeded`, and is not charged. Tests stop after 2 failures in a row, after a wrong key or quota error, or after a failed test that took over 30 s (a test is stopped at 60 s). PageSpeed is skipped when the site stopped answering (`SOURCE_ERROR`).
- **Run memory** is fixed at 1024 MB; the default run timeout is 3600 s.

`RUN_SUMMARY.status` tells you how complete the run is:

| `status` | `stopReason` | Meaning |
| --- | --- | --- |
| `complete` | `COMPLETED` | All reachable pages within your limits were checked. |
| `partial` | `MAX_PAGES` | `maxPages` reached; the site has more pages. |
| `partial` | `TIME_LIMIT`, `CHARGE_LIMIT`, `ABORTED` | The run timeout, charge limit or a manual abort stopped the crawl. All checked pages are saved. |
| `partial` | `SOURCE_ERROR` | 10 pages in a row failed; the site stopped answering. Sitemap and file checks are skipped. The run fails, saved rows stay. |
| `error` | `START_URL_HTTP_ERROR` | The start URL answered 4xx (run succeeds) or 5xx (run fails). One free row. |
| `error` | `START_URL_FAILED`, `ROBOTS_BLOCKED` | The start URL could not be loaded or `robots.txt` blocks it. |
| `error` | `INPUT_ERROR` | The input is not valid; the message says which field to fix. |

### Troubleshooting

#### Only one row, with status 404 or 500

Symptom: the dataset has one free row and `stopReason` is `START_URL_HTTP_ERROR`.
Reason: the start URL itself answers with an error.
Action: open the URL in a browser and use the address of a working page, usually the homepage.

#### Fewer pages than expected

Check `RUN_SUMMARY.stopReason` and the counters `blockedByRobots`, `excludedByPattern` and `urlsNotCrawled`. Common reasons are `maxPages`, `maxDepth`, `excludeUrlPatterns`, `robots.txt` rules, or links that are added by JavaScript only.

#### Missing H1 or very low word count on every page

The site probably renders its content with JavaScript. The Actor sees the server HTML only, so these numbers describe what a crawler without JavaScript gets.

#### Many `restricted_external_links`

Social networks and some large sites block automated requests. Open these links in a browser to confirm they work. They are not counted as broken.

#### The run is slow

`robots.txt` may set `Crawl-delay`, so the Actor checks one page at a time and waits. PageSpeed tests also add about 10–30 seconds each.

### FAQ

#### Do I need an account on the site or an API key?

No. The Actor checks public pages without login. A Google API key is needed only for the optional PageSpeed tests.

#### Does it run JavaScript like a browser?

No. It reads the HTML that the server returns, like most search-engine crawls do first. This keeps it fast and cheap, but content added by scripts is not seen.

#### Am I charged for 404 pages and redirects?

Yes, when they are on your site: a page that answers 404 or 301 is a checked page and an important finding. Pages that give no answer at all are free. When the start URL itself fails, only the start fee is charged.

#### Can I audit several websites in one run?

No, one start URL per run. Start one run per site, or create a task for each site.

#### Is the result complete?

It is complete for the pages the crawler can reach by links from the start URL, within your limits and `robots.txt`. Pages that no link points to (orphan pages) are not found.

### Related Actors

- [Bulk URL Status & Redirect Checker](https://apify.com/cliqtomedia/bulk-url-status-redirect-checker) — check status codes and redirects for your own list of URLs, without crawling.
- [XML Sitemap URLs Extractor](https://apify.com/cliqtomedia/xml-sitemap-urls-extractor) — get every URL from a sitemap, then compare it with the crawled pages.
- [Website Metadata & Open Graph Scraper](https://apify.com/cliqtomedia/website-metadata-open-graph-scraper) — Open Graph and social preview tags for a list of URLs.
- [Tech Stack Detector](https://apify.com/cliqtomedia/tech-stack-detector) — find the CMS, frameworks and tools a website uses.

### Support

For bugs, questions or feature requests, open an issue on the **Issues** tab of this Actor. Add the run ID, your input without the API key and the `RUN_SUMMARY` message. Never share your Google API key or other secrets.

# Changelog

This Actor's version history is a separate document: https://apify.com/cliqtomedia/seo-site-audit/changelog.md

# Actor input Schema

## `startUrl` (type: `string`):

Page where the crawl starts, usually the homepage. Only pages on the same host (www and non-www count as the same) are crawled.

## `maxPages` (type: `integer`):

Maximum number of URLs to audit. You pay per audited page that returned an HTTP response.

## `maxDepth` (type: `integer`):

How many clicks away from the start URL to go. Leave empty for no limit.

## `checkExternalLinks` (type: `boolean`):

Send a HEAD request (GET if HEAD is refused) to links pointing to other sites and report broken ones.

## `maxExternalLinks` (type: `integer`):

Unique external URLs to check per run, at most 20 per audited page. External checks are not charged.

## `includeSubdomains` (type: `boolean`):

Also crawl subdomains such as blog.example.com when they are linked.

## `excludeUrlPatterns` (type: `array`):

Skip URLs containing any of these strings. Use \* as a wildcard, e.g. */tag/* or ?page=.

## `maxConcurrency` (type: `integer`):

Requests to the site at the same time (1–5). If robots.txt sets Crawl-delay, the Actor uses 1 and waits between pages (up to 10 s).

## `userAgent` (type: `string`):

Leave empty to use the Actor's own honest crawler name, which robots.txt rules can target. Names of search-engine bots are not allowed.

## `pagespeedApiKey` (type: `string`):

Your own Google API key with PageSpeed Insights API enabled. Adds Lighthouse performance score, LCP, CLS and TBT for the first pages. Never logged or stored.

## `pagespeedMaxPages` (type: `integer`):

How many audited pages (in crawl order, start URL first) get a PageSpeed test. A test usually takes 10–30 s and is stopped after 60 s. Only successful tests are charged; tests stop after 2 failures in a row or after a slow failed test.

## `pagespeedStrategy` (type: `string`):

Lighthouse device profile.

## Actor input object example

```json
{
  "startUrl": "https://apify.com",
  "maxPages": 20,
  "checkExternalLinks": true,
  "maxExternalLinks": 300,
  "includeSubdomains": false,
  "maxConcurrency": 5,
  "pagespeedMaxPages": 1,
  "pagespeedStrategy": "mobile"
}
```

# Actor output Schema

## `pages` (type: `string`):

No description

## `reportHtml` (type: `string`):

No description

## `report` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# 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 = {
    "startUrl": "https://apify.com",
    "maxPages": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("cliqtomedia/seo-site-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 = {
    "startUrl": "https://apify.com",
    "maxPages": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("cliqtomedia/seo-site-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 '{
  "startUrl": "https://apify.com",
  "maxPages": 20
}' |
apify call cliqtomedia/seo-site-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cliqtomedia/seo-site-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/0ac6QTSF1qF7PZj9s/builds/SUm9mjfZgoDd3kxAJ/openapi.json
