# SEO Page Audit: Meta, Headings, Links, Schema & Redirects (`ataism/seo-page-audit`) Actor

SEO page audit over plain HTTP: meta, headings, canonical, schema, hreflang, redirects, links and a 0-100 score per page. $6 per 1,000 pages. Errors are free.

- **URL**: https://apify.com/ataism/seo-page-audit.md
- **Developed by:** [Kaizoku Data](https://apify.com/ataism) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.00 / 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.

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 Page Audit: Meta, Headings, Links, Schema & Redirects

Audit the pages you list, or crawl one site from a start URL, over plain HTTP and get **one dataset row per page**: redirect chain, canonical, robots directives, title and description with length flags, headings, Open Graph and Twitter tags, JSON-LD types and errors, hreflang, image alt coverage, link counts, mixed content, and an `issues[]` list with a 0-100 score. No browser, no JavaScript, no proxy, no API key.

**Price: $6 per 1,000 audited pages ($0.006 per page).** Pages that cannot be audited (network errors, robots.txt blocks, non-HTML, refused access) are free.

- **The whole on-page checklist in one row:** status, final URL, redirects, response time, canonical (and whether it points at itself), meta robots plus `X-Robots-Tag`, title and meta description lengths, H1 count and texts, heading outline, `og:` and `twitter:` tags, JSON-LD types with JSON parse errors, hreflang (with an optional return-link check), `lang`, viewport, image and alt counts, internal and external links, word count, mixed content on https pages.
- **A score you can sort by:** every row has `issues[]` (severity, code, message) and a `score` from 0 to 100. The weights are in [How the score works](#how-the-score-works).
- **Optional extras, included in the page price:** link status checks (HEAD, then GET), hreflang return-link check, and a per-site summary of `robots.txt` and `sitemap.xml`.
- **Polite by design:** `robots.txt` is obeyed (a disallowed page becomes a free `blocked-by-robots` row), at most 1 request per second per host by default (4 at most), a `Crawl-delay` in robots.txt honoured in full (never shortened; over 2 seconds only with `allowSlowHosts`, over 10 seconds never), an honest User-Agent, no JavaScript, and it never works around blocks.
- **Quick start:** press *Start*. The prefilled input audits `https://quotes.toscrape.com/` (a public scraping sandbox: one page, a few seconds, $0.006).

> **Use it on sites you own or are allowed to audit.** The Actor only fetches the URLs you give it and, if you switch crawling on, links on the same host. It reads server-rendered HTML: content that a browser adds with JavaScript is not seen (see [Limits](#limits-and-honest-caveats)).

### Input

| Field | What it does |
|---|---|
| **Page URLs / start URLs** (`startUrls`) | One URL per line, at most 500. A bare host gets `https://`. Each URL is audited as one page. With crawling on, they are the crawl's starting points. Default: `https://quotes.toscrape.com/`. |
| **Crawl the whole site** (`crawlSite`) | Off by default. On: follow links to other pages on the **same host** (exact host name; `www.example.com` and `example.com` are different hosts) until *Max pages*. Disallowed links become free `blocked-by-robots` rows. |
| **Max pages (crawl only)** (`maxPages`) | The most page rows the whole crawl produces, audited and free rows together. Default 1, maximum 500. Not used when crawling is off: then every listed URL is audited. |
| **Requests per second per host** (`requestsPerSecond`) | 1 to 4, default 1. Applies to every request to a host (pages, robots.txt, sitemaps, link and hreflang checks). A `Crawl-delay` in robots.txt is honoured in full (never shortened) when it is longer than this gap. |
| **Allow slow hosts** (`allowSlowHosts`) | Off by default. Off: a site whose robots.txt asks for a `Crawl-delay` over 2 seconds is skipped with free `slow-host` rows. On: it is audited and the delay is honoured in full for every request to that host (pages, link and hreflang checks, sitemaps), so the run takes longer; hosts that ask for more than 10 seconds are still skipped (free `slow-host` rows, "over the 10 s maximum this Actor honours"). The waiting is billed as run time on your account: at a 10 second delay one page costs about 9% of its price in compute at $0.20 per compute unit. |
| **Check link status** (`checkLinks`) | Off by default. Checks the first N unique links of each page with HEAD, falling back to GET. Included in the page price. |
| **Links to check per page** (`maxLinksPerPage`) | Default 10, maximum 25. Internal links come first. A link seen on many pages is checked once per run; at most 5,000 link requests per run. |
| **Check hreflang return links** (`checkHreflang`) | Off by default. Fetches up to 10 alternate pages per page and checks that they link back. |
| **Summarise robots.txt and sitemap.xml** (`siteFiles`) | Off by default. Adds one `rowType: "site"` row per site (at most 10 sites). Not charged. |
| **Timeout per page** (`timeoutSecs`) | 5 to 60, default 30. |

#### Example inputs

**1. Audit a handful of pages and check their links:**

```json
{
  "startUrls": ["https://example.com/", "https://example.com/pricing", "https://example.com/blog"],
  "checkLinks": true,
  "maxLinksPerPage": 15
}
```

**2. Crawl a small site, 200 pages, and summarise its robots.txt and sitemaps:**

```json
{
  "startUrls": ["https://example.com/"],
  "crawlSite": true,
  "maxPages": 200,
  "siteFiles": true
}
```

**3. International site: hreflang return links, gentle rate:**

```json
{
  "startUrls": ["https://example.com/en/", "https://example.com/de/"],
  "checkHreflang": true,
  "requestsPerSecond": 1
}
```

### Output

One dataset row per page. Abbreviated example:

```json
{
  "rowType": "page",
  "inputIndex": 0,
  "url": "http://example.com/shop",
  "status": "audited",
  "finalUrl": "https://example.com/shop/",
  "httpStatus": 200,
  "redirectChain": [{ "url": "http://example.com/shop", "status": 301 }, { "url": "https://example.com/shop", "status": 301 }],
  "redirectCount": 2,
  "responseTimeMs": 212,
  "https": true,
  "lang": "en",
  "title": "Shop: Blue Widgets for Home and Garden",
  "titleLength": 38,
  "titleFlag": "ok",
  "metaDescription": null,
  "metaDescriptionFlag": "missing",
  "canonical": { "url": "https://example.com/shop/", "count": 1, "isSelf": true },
  "robots": { "meta": "index, follow", "xRobotsTag": null, "noindex": false, "nofollow": false },
  "h1Count": 1,
  "h1Texts": ["Blue Widgets"],
  "headings": { "h1": 1, "h2": 4, "h3": 6, "h4": 0, "h5": 0, "h6": 0, "skippedLevels": false },
  "openGraph": { "title": "Shop", "image": "https://example.com/og.png" },
  "twitter": { "card": "summary" },
  "jsonLd": { "blocks": 2, "types": ["BreadcrumbList", "Organization"], "parseErrors": [] },
  "hreflang": [{ "lang": "en", "href": "https://example.com/shop/", "valid": true }],
  "images": { "count": 14, "missingAlt": 2, "emptyAlt": 3 },
  "links": { "total": 63, "internal": 51, "external": 12, "internalUnique": 40, "externalUnique": 11, "nofollow": 3 },
  "wordCount": 612,
  "viewport": "width=device-width, initial-scale=1",
  "mixedContent": { "found": false, "count": 0, "examples": [] },
  "issues": [
    { "severity": "warning", "code": "meta-description-missing", "message": "No meta description." },
    { "severity": "warning", "code": "images-missing-alt", "message": "2 of 14 images have no alt attribute." }
  ],
  "score": 90,
  "scoreVersion": 1,
  "processedAt": "2026-10-20T10:00:00.000Z"
}
```

With *Check link status* a `linkCheck` object is added (`checked`, `ok`, `restricted`, `skipped`, `broken[]`, `brokenInternal`, `brokenExternal`). With *Check hreflang return links* an `hreflangCheck` object is added (`missingReturn[]`, `unreachable[]`). With *Summarise robots.txt and sitemap.xml* each site gets a row with `rowType: "site"`, `robotsTxt` and `sitemaps[]` (kind, URL count, newest `lastmod`, a few sample URLs).

The run summary (key-value store record `RUN_SUMMARY`) has counts, charged events, bytes downloaded, failures by code and why the run stopped.

#### Status values (only `audited` is charged)

| `status` | Meaning | Charged |
|---|---|---|
| `audited` | The page was fetched (any HTTP status, a 404 page included) and analysed. | **Yes, $0.006** |
| `blocked-by-robots` | `robots.txt` disallows this URL (or a redirect target). It was not requested. | No |
| `non-html` | The URL returned something other than HTML (PDF, JSON, image, ...). | No |
| `error` | See `errorCode` and `message`. | No |

Error codes: `invalid-url`, `blocked` (private, local or internal address), `platform` (see Privacy and safety), `dns`, `timeout`, `network`, `too-large` (HTML over 5 MB), `redirects` (more than 5, or a loop), `site-blocked` (the site answered 403, 429 or a bot challenge), `host-blocking` (the host already refused twice in this run), `robots-unreachable` (its robots.txt could not be read, so the Actor does not guess), `slow-host` (the host's robots.txt asks for a `Crawl-delay` over 2 seconds and `allowSlowHosts` is off, or over the 10 s maximum this Actor honours, which is skipped even with `allowSlowHosts`; the delay is never shortened; free, nothing was fetched from the host except its robots.txt; also reported for a redirect target, a link, an hreflang alternate or a sitemap on such a host), `analysis-failed` (analysis timeout, memory limit or nesting over 1,000 levels), `push-failed` (the row could not be saved), `unexpected`.

A page that answers 4xx or 5xx other than 403 and 429 is a valid audit result: it is charged and gets a `critical` `http-error` issue.

### How the score works

`score = 100 - 40 per critical issue code - 15 per error code - 5 per warning code - 0 per info code`, never below 0. Each issue code counts once per page, however many times it occurs. `scoreVersion` is 1; if the weights change, the version changes.

| Severity | Codes |
|---|---|
| **critical** (-40) | `http-error` (status 400 or above) |
| **error** (-15) | `title-missing`, `h1-missing`, `canonical-multiple`, `canonical-invalid`, `noindex`, `jsonld-parse-error`, `mixed-content`, `broken-internal-links` |
| **warning** (-5) | `not-https`, `redirect-chain-long` (3+ redirects), `slow-response` (first byte over 2,000 ms), `title-too-long` (over 60 characters), `meta-description-missing`, `h1-multiple`, `h1-empty`, `canonical-missing`, `canonical-not-self`, `nofollow`, `lang-missing`, `viewport-missing`, `hreflang-invalid-code`, `hreflang-no-self`, `hreflang-missing-return`, `images-missing-alt`, `thin-content` (under 100 words), `broken-external-links` |
| **info** (0) | `redirect-temporary` (302/307 in the chain), `title-too-short` (under 30), `meta-description-too-short` (under 70), `meta-description-too-long` (over 160), `heading-levels-skipped`, `open-graph-incomplete`, `twitter-card-missing`, `jsonld-none`, `hreflang-no-x-default` |

The score is a checklist summary of what the Actor can see in the HTML and headers. It is not a prediction of rankings and not a Google metric. The title and description length thresholds are common rules of thumb in characters, not pixel widths. `responseTimeMs` is time to first byte measured from the Actor's server, not a Core Web Vital.

### Pricing

Pay per event: **$0.006 per page with status `audited`**, plus Apify's standard Actor-start event. There is no additional start fee from this Actor. Link checks, hreflang checks and site-file summaries are included in the page price.

| Pages audited | Cost |
|---|---|
| 1 | $0.006 |
| 10 | $0.06 |
| 100 | $0.60 |
| 500 | $3.00 |
| 1,000 | $6.00 |

Set **Max total charge** in the run options to cap spending: the run stops cleanly when the limit is reached and never charges for a row it did not deliver. Apify subscription discounts apply on top.

### Use it via API, n8n or Make

Get an API token in Apify Console, *Settings*, *API & Integrations*, and keep it in an environment variable (`APIFY_TOKEN` below). Never paste it into shared code.

**cURL** (starts a run, waits for it and returns one row per page; the synchronous endpoint waits up to 5 minutes, so use it for a few pages and the clients below for big crawls):

```bash
curl -X POST "https://api.apify.com/v2/acts/ataism~seo-page-audit/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"startUrls": ["https://quotes.toscrape.com/"], "maxPages": 1}'
```

**Python** (`pip install "apify-client>=3"`):

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("ataism/seo-page-audit").call(run_input={
    "startUrls": ["https://example.com/"],
    "crawlSite": True,
    "maxPages": 50,
})
for page in client.dataset(run.default_dataset_id).iterate_items():
    if page.get("status") != "audited":
        print("not audited (free):", page["url"], page["status"])
        continue
    errors = [i["code"] for i in page["issues"] if i["severity"] in ("critical", "error")]
    print(page["score"], page["finalUrl"], errors)
```

(With apify-client versions before 3.0, `call()` returns a dict: use `run["defaultDatasetId"]`.)

**JavaScript** (`npm install apify-client`):

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('ataism/seo-page-audit').call({
    startUrls: ['https://example.com/', 'https://example.com/pricing'],
    checkLinks: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const page of items) console.log(page.score, page.finalUrl, page.issues?.length ?? 0);
```

**n8n:**

1. Start with any trigger that gives you page URLs (a schedule, a form, a sheet row).
2. Add the **Apify** node, operation **Run an Actor and get dataset**, Actor `ataism/seo-page-audit`, input `{"startUrls": ["{{ $json.url }}"]}`.
3. Filter on `score` or on `issues` in the next node and send the failures to Slack, email or a sheet.

**Make:**

1. Add the **Apify** module **Run an Actor**, choose `ataism/seo-page-audit`, put your URLs in the input JSON and set *Run synchronously* to *Yes*.
2. Add the **Apify** module **Get Dataset Items** and map the run's default dataset ID (one bundle per page).
3. Route low scores to your next module (a sheet, a ticket, a message).

### Privacy and safety

- **What is stored:** only the dataset rows you asked for and the run summary (plus a `PROGRESS` record used to resume after a restart). Page text is not stored: rows hold counts, flags, tags, link counts, and short fields (title, descriptions, H1 texts, Open Graph and Twitter text).
- **Contact details are removed from those short fields.** Email addresses and phone numbers are replaced by `[email removed]` and `[phone removed]`, and the same is applied to URLs in the output. Page titles and headings can still contain other personal information (a person's name on a profile page, for example); this Actor does not detect names. Audit pages whose content you are entitled to process.
- **Public addresses only.** Only `http` and `https` URLs to public addresses are fetched. Private, loopback, link-local and internal addresses are blocked, including after redirects (at most 5, each re-checked) and at connection time.
- **Platforms are refused.** YouTube, TikTok, Instagram, Facebook, X/Twitter, Vimeo, SoundCloud, Spotify, Twitch, Dailymotion, Reddit, LinkedIn and Google domains return error code `platform` before any DNS lookup or request.
- **No workarounds.** If a site answers 403 or 429, or shows a bot challenge, the row is an uncharged `site-blocked` error and the Actor stops sending requests to that host after two such answers. It does not retry, rotate addresses or fake a browser.
- **You are responsible for having the right to audit the sites you give it** (your own sites, or sites whose owners allow it). Check the target's terms. The Actor obeys `robots.txt` and keeps request rates low, but that does not replace permission.

### Limits and honest caveats

- **No JavaScript.** Single-page apps that render content in the browser will show little or no content in the HTML (low `wordCount`, missing H1, `thin-content`). A fetched page is analysed as the server sent it.
- **HTML up to 5 MB** per page; larger pages are `too-large` errors, not charged. At most **500 URLs** in the list and **500 pages** per crawl, at most 25 link checks per page and 5,000 per run, at most 10 hreflang alternates per page, at most 8 sitemap files per site (5 MB each, 10 MB unpacked). Sitemap files are fetched under the same rules as pages: robots.txt, the refusal rules below and the request rate.
- **Run-level safeguards for unpaid work:** the run stops after 50 pages that could not be audited or that only led to a page already audited (for example a link that redirects to the home page), 100 MB downloaded for them, or 10 minutes spent on them, and after 60 minutes in total (`stoppedBy` in the run summary says which).
- **Crawl-delay:** a `Crawl-delay` in robots.txt is honoured in full for every request to that host (pages, redirect hops, link and hreflang checks, sitemap files), counted from the moment its robots.txt was answered, so the first page waits too. Waiting is not part of the request timeout. Over 2 seconds the host is "slow": it is skipped (free `slow-host` rows) unless you set `allowSlowHosts`, because waiting between requests makes the run long and its compute cost can exceed the page price; over 10 seconds it is skipped even then (free `slow-host` rows; the delay is never shortened to fit). Link and hreflang checks of a page do not wait past their 60 s budget: a link whose host would make them wait longer is counted as `skipped`.
- **Speed:** 1 request per second per host by default, so a 500-page crawl of one site takes about 8 to 10 minutes; link checks on one host add to that. Several hosts in a list are fetched in parallel.
- **robots.txt:** one file per host per run; the group whose user agent is exactly `seo-page-audit-actor` is used if present, otherwise `*`; the longest matching rule wins. If a robots.txt answers 5xx or 429 or cannot be fetched, that host's pages are not fetched (free `robots-unreachable` rows). A robots.txt that is missing (404) means everything is allowed. Only the first 512 KB of a robots.txt are read (the last, possibly cut, line is dropped); rules after that point are ignored.
- **Link checks:** 401, 403, 429 and 999 answers are reported as `restricted`, not broken, and are never retried; GET is tried only when HEAD gets 405, 501 or fails to connect. Two 403/429 answers from a host stop all further link, hreflang and page requests to it in this run. Links the robots.txt of their host disallows are skipped.
- **Hreflang** is read from `<link rel="alternate" hreflang>` tags in the HTML only (not from HTTP headers or sitemaps).
- **X-Robots-Tag:** a `bot: directive` token applies that bot's name to the tokens after it; unprefixed tokens and those for `googlebot` or `bingbot` are applied.
- **Word count** is whitespace-separated tokens in the HTML body without scripts and styles, so it is only a rough measure for languages written without spaces.
- **Crawl:** same exact host, links from `<a href>` only; file links (images, PDFs, archives and similar) are not followed; a page that redirects to an already audited URL is dropped from a crawl (not charged); pages with a `nofollow` robots directive are audited but their links are not followed.
- Memory: default 1,024 MB. Pages are analysed in worker threads limited to 384 MB each and 20 s, so a pathological page (for example 20,000 nested elements, or HTML nested more than 1,000 levels deep) becomes a free `analysis-failed` row instead of stopping the run; the number of pages in flight follows the run's memory (1,024 MB gives 2).
- **Time per page for the optional checks:** link and hreflang checks share a 60 s budget per page, and each request has a 10 s timeout; links not reached in time are counted as `skipped`.

### FAQ

**Why was my page not charged?** Only `audited` rows are charged. Check `status`, `errorCode` and `message`.

**Why is a 404 page charged?** A page that answers 404 or 500 was fetched and analysed, and the result (status, redirect chain, response time, headers) is the audit finding. Refusals (403, 429, bot challenges), network errors, robots.txt blocks and non-HTML answers are free.

**Can it audit pages behind a login or a JavaScript app?** No.

**Does the default run work without setup?** Yes. It audits `https://quotes.toscrape.com/` (a public sandbox site meant for scraping and testing), one page.

### Open-source components

HTML parsing uses **cheerio** (MIT) and the **Apify SDK** (`apify` 3.7.2, Apache-2.0). Their dependencies are permissively licensed.

# Actor input Schema

## `startUrls` (type: `array`):

One URL per line (http or https; a bare host gets https://). Each URL is audited as one page. With "Crawl the whole site" on, they are the places the crawl starts from. At most 500. Private, local and internal addresses are blocked.

## `crawlSite` (type: `boolean`):

Off (default): audit only the URLs listed above. On: follow links to other pages on the same host (exact host name, no subdomains) until Max pages is reached. Links to pages disallowed by robots.txt become "blocked-by-robots" rows that are not charged.

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

Crawl limit: the most page rows the whole run produces when "Crawl the whole site" is on (audited pages and uncharged rows together). Default 1, maximum 500. Ignored when crawling is off, where every listed URL is audited (up to 500).

## `requestsPerSecond` (type: `integer`):

Politeness limit for every request sent to one host (pages, robots.txt, sitemaps, link checks). Default 1, maximum 4. A Crawl-delay in robots.txt is honoured in full (never shortened) when it is longer than this gap; see Allow slow hosts.

## `allowSlowHosts` (type: `boolean`):

Off: a site whose robots.txt asks for a Crawl-delay over 2 seconds is skipped (free "slow-host" rows), because waiting between requests makes the run slow and the compute cost can exceed the page price. On: sites with a delay of 2 to 10 seconds are audited and the delay is honoured in full for every request to the host (pages, link checks, hreflang checks, sitemaps), so the run takes longer and the waiting is billed as run time (a 10 second delay costs about 9% of the page price in compute at $0.20 per compute unit). Sites that ask for more than 10 seconds are still skipped. Default off.

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

Sends HEAD requests (GET if HEAD fails) to links found on each audited page and reports broken ones. Included in the page price, but it makes the run slower because of the per-host delay. Links disallowed by robots.txt are skipped. Default off.

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

Only the first N unique links of each page are checked (internal links first). Default 10, maximum 25. At most 5,000 link requests per run; repeated links are checked once.

## `checkHreflang` (type: `boolean`):

Fetches up to 10 alternate pages named in each page's hreflang tags and checks that they link back. Default off.

## `siteFiles` (type: `boolean`):

Adds one extra row per site (rowType "site") with the robots.txt rules that apply to this Actor and a summary of up to 8 sitemap files. Not charged. Default off.

## `timeoutSecs` (type: `integer`):

Per page, including redirects. Default 30, maximum 60.

## `mode` (type: `string`):

Internal: "selfTest" runs the maintainer health check and saves no items.

## Actor input object example

```json
{
  "startUrls": [
    "https://quotes.toscrape.com/"
  ],
  "crawlSite": false,
  "maxPages": 1,
  "requestsPerSecond": 1,
  "allowSlowHosts": false,
  "checkLinks": false,
  "maxLinksPerPage": 10,
  "checkHreflang": false,
  "siteFiles": false,
  "timeoutSecs": 30,
  "mode": "run"
}
```

# Actor output Schema

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

One item per page with status, redirect chain, meta tags, headings, links, structured data, issues and score (JSON, CSV, Excel or XML). With the site-files option, one extra item per site.

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

Pages audited and charged, rows not charged and why, bytes downloaded and why the run stopped.

# 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 = {
    "startUrls": [
        "https://quotes.toscrape.com/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ataism/seo-page-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 = { "startUrls": ["https://quotes.toscrape.com/"] }

# Run the Actor and wait for it to finish
run = client.actor("ataism/seo-page-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 '{
  "startUrls": [
    "https://quotes.toscrape.com/"
  ]
}' |
apify call ataism/seo-page-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ataism/seo-page-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/ccMyoTyYvTWCQPblT/builds/z7yLA9HuQYq3o0xCJ/openapi.json
